@circuitwall/jarela 2.2.0 → 2.2.1

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.
Files changed (86) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -2
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  13. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/api/v1/models/[name]/route.js +61 -20
  19. package/.next/standalone/.next/server/app/api/v1/models/route.js +5 -3
  20. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js +27 -24
  21. package/.next/standalone/.next/server/app/api/v1/update/apply/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/page.js +313 -251
  23. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app-paths-manifest.json +2 -2
  26. package/.next/standalone/.next/server/chunks/2471.js +17 -17
  27. package/.next/standalone/.next/server/chunks/2563.js +24 -50
  28. package/.next/standalone/.next/server/chunks/3399.js +10695 -10695
  29. package/.next/standalone/.next/server/chunks/3524.js +5 -33
  30. package/.next/standalone/.next/server/chunks/424.js +10 -5
  31. package/.next/standalone/.next/server/chunks/4508.js +2 -2
  32. package/.next/standalone/.next/server/chunks/46.js +111 -46
  33. package/.next/standalone/.next/server/chunks/466.js +88 -13
  34. package/.next/standalone/.next/server/chunks/6649.js +1 -1
  35. package/.next/standalone/.next/server/chunks/9590.js +108 -46
  36. package/.next/standalone/.next/server/chunks/9640.js +3 -6
  37. package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
  38. package/.next/standalone/.next/server/middleware.js +104 -45
  39. package/.next/standalone/.next/server/pages/404.html +1 -1
  40. package/.next/standalone/.next/server/pages/500.html +1 -1
  41. package/.next/standalone/.next/server/preview-props.json +3 -3
  42. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  43. package/.next/standalone/.next/static/chunks/{1395-f201e3e2e1f57715.js → 1395-a340fb0a345382a4.js} +89 -14
  44. package/.next/standalone/.next/static/chunks/{4576-13f7546a145a3624.js → 7045-8bc2f14e430ca661.js} +58 -2
  45. package/.next/standalone/.next/static/chunks/app/{page-c6e972a7fd13dae8.js → page-7017f07f8a7a083c.js} +170 -151
  46. package/.next/standalone/package.json +1 -1
  47. package/CHANGELOG.md +8 -0
  48. package/api/client.models.test.ts +31 -0
  49. package/api/client.ts +16 -1
  50. package/api/types.ts +8 -0
  51. package/app/api/v1/models/[name]/route.test.ts +89 -0
  52. package/app/api/v1/models/[name]/route.ts +54 -3
  53. package/app/api/v1/models/route.ts +4 -2
  54. package/app/api/v1/providers/[provider]/models/route.test.ts +53 -0
  55. package/app/api/v1/providers/[provider]/models/route.ts +27 -16
  56. package/components/agents/agent-editor/AdvancedSection.tsx +8 -2
  57. package/components/agents/agent-editor/ModelSection.tsx +6 -3
  58. package/components/models/ModelEditor.tsx +1 -1
  59. package/components/models/ModelsPanel.tsx +34 -8
  60. package/components/models/model-editor/GitHubCopilotAuth.tsx +1 -1
  61. package/components/models/model-editor/save-helpers.ts +2 -2
  62. package/components/models/model-editor/useModelEditorForm.ts +1 -1
  63. package/components/models/model-editor/useModelSaveHandlers.ts +1 -1
  64. package/components/setup/wizard/StepCredentials.tsx +1 -1
  65. package/components/setup/wizard/StepModel.tsx +3 -8
  66. package/hooks/useModels.ts +17 -1
  67. package/lib/agents/effective-model.test.ts +45 -0
  68. package/lib/agents/effective-model.ts +11 -5
  69. package/lib/agents/llm.ts +2 -2
  70. package/lib/agents/run-thread.ts +10 -5
  71. package/lib/agents/warm-summary-background.test.ts +1 -2
  72. package/lib/agents/warm-summary-background.ts +4 -7
  73. package/lib/db/migrations.ts +10 -0
  74. package/lib/health/probes.test.ts +8 -11
  75. package/lib/health/probes.ts +5 -31
  76. package/lib/models/editor-payload.ts +1 -1
  77. package/lib/providers/github-copilot-auth.ts +2 -3
  78. package/lib/providers/github-copilot.test.ts +27 -0
  79. package/lib/providers/github-copilot.ts +21 -44
  80. package/lib/stores/integrations.test.ts +7 -0
  81. package/lib/stores/integrations.ts +4 -4
  82. package/lib/stores/model-config.test.ts +88 -0
  83. package/lib/stores/model-config.ts +90 -8
  84. package/package.json +1 -1
  85. /package/.next/standalone/.next/static/{e2NV1wk6mWg3SIeELXevg → NUrC9xax1sp1Q70nlFqS6}/_buildManifest.js +0 -0
  86. /package/.next/standalone/.next/static/{e2NV1wk6mWg3SIeELXevg → NUrC9xax1sp1Q70nlFqS6}/_ssgManifest.js +0 -0
@@ -1855,13 +1855,8 @@ async function probeCohere() {
1855
1855
  }
1856
1856
  }
1857
1857
  async function probeGithubCopilot() {
1858
- // Two credential paths share this probe:
1859
- // 1. Device-flow OAuth token (stored under memory_store namespace
1860
- // "github-copilot-auth" by the in-app sign-in). Verified by
1861
- // exchanging it for a Copilot session token.
1862
- // 2. Personal Access Token saved via the Integrations panel.
1863
- // PATs cannot be exchanged, so we check them against the GitHub
1864
- // Models REST surface which Copilot PATs are routed to.
1858
+ // Device-flow OAuth token stored under the github-copilot-auth namespace.
1859
+ // Legacy PAT credentials used GitHub Models, which has been retired.
1865
1860
  const oauth = (0,_lib_providers_github_copilot_auth__WEBPACK_IMPORTED_MODULE_8__.getStoredOAuthToken)();
1866
1861
  if (oauth) {
1867
1862
  try {
@@ -1892,32 +1887,9 @@ async function probeGithubCopilot() {
1892
1887
  return transient(describeError(err));
1893
1888
  }
1894
1889
  }
1895
- const pat = (0,_lib_stores_integrations__WEBPACK_IMPORTED_MODULE_7__/* .getIntegrationRaw */ .$G)("github-copilot")?.api_key?.trim();
1896
- if (!pat) return unconfigured("Not signed in and no PAT configured");
1897
- try {
1898
- const res = await fetch("https://models.github.ai/catalog/models", {
1899
- headers: {
1900
- Authorization: `Bearer ${pat}`,
1901
- Accept: "application/vnd.github+json",
1902
- "X-GitHub-Api-Version": "2026-03-10",
1903
- "User-Agent": "Jarela"
1904
- },
1905
- signal: probeSignal(DEFAULT_PROBE_TIMEOUT_MS)
1906
- });
1907
- if (res.status === 401 || res.status === 403) {
1908
- return authFailed(`GitHub rejected the PAT (${res.status}). Confirm the token has the "copilot" scope and your subscription is active.`);
1909
- }
1910
- if (res.status === 429) return transient("GitHub rate-limited the probe (429).");
1911
- if (!res.ok) {
1912
- const body = await res.text().catch(()=>"");
1913
- return probeError(`GitHub Models ${res.status}: ${body.slice(0, 200)}`);
1914
- }
1915
- return ok({
1916
- auth: "pat"
1917
- });
1918
- } catch (err) {
1919
- return transient(describeError(err));
1920
- }
1890
+ const legacyPat = (0,_lib_stores_integrations__WEBPACK_IMPORTED_MODULE_7__/* .getIntegrationRaw */ .$G)("github-copilot")?.api_key?.trim();
1891
+ if (!legacyPat) return unconfigured("Not signed in. Use device-flow sign-in from the Models panel.");
1892
+ return authFailed("GitHub PAT authentication is no longer supported. Use device-flow sign-in from the Models panel.");
1921
1893
  }
1922
1894
  async function probeClaudeCode() {
1923
1895
  const cfg = (0,_lib_tools_delegation_claude_code_config__WEBPACK_IMPORTED_MODULE_9__/* .getClaudeCodeConfig */ .G)();
@@ -19502,7 +19502,7 @@ function streamWithConfig(threadId, messages, options, signal) {
19502
19502
  async function* streamWithConfigImpl(threadId, messages, options, signal) {
19503
19503
  const runCfg = options?.agent_run_config;
19504
19504
  const cfgName = runCfg?.model_config_name ?? null;
19505
- const cfg = (cfgName ? (0,model_config/* getModelConfig */.Ac)(cfgName) : null) ?? (0,model_config/* getDefaultModelConfig */.lB)();
19505
+ const cfg = (0,model_config/* resolveRunnableModelConfig */.np)(cfgName);
19506
19506
  if (!cfg) {
19507
19507
  yield {
19508
19508
  type: "error",
@@ -22136,9 +22136,11 @@ function snapshotThreadModelConfigName(thread_id) {
22136
22136
  if (!thread) return null;
22137
22137
  const agentCfg = (0,agent_configs/* getAgentConfig */.mJ)(thread.agent_id);
22138
22138
  if (!agentCfg) return null;
22139
- if (agentCfg.model_config_name) return agentCfg.model_config_name;
22139
+ if (agentCfg.model_config_name && (0,model_config/* isModelConfigEnabled */.FO)((0,model_config/* getModelConfig */.Ac)(agentCfg.model_config_name))) {
22140
+ return agentCfg.model_config_name;
22141
+ }
22140
22142
  if ((0,config/* getConfig */.z)().modelRouterMode !== "off") return null;
22141
- return agentCfg.model_config_name ?? (0,model_config/* getDefaultModelConfig */.lB)()?.name ?? null;
22143
+ return (0,model_config/* getDefaultModelConfig */.lB)()?.name ?? null;
22142
22144
  }
22143
22145
  function appendHistoryMessage(history, injected) {
22144
22146
  return injected === undefined ? history : [
@@ -22234,7 +22236,9 @@ function appendHistoryMessage(history, injected) {
22234
22236
  // the warm-summary recursion inside buildHistoryWindow).
22235
22237
  const defaultModelConfig = (0,model_config/* getDefaultModelConfig */.lB)();
22236
22238
  let modelConfigName = req._pinned_model_config_name ?? agentCfg.model_config_name ?? null;
22237
- if (modelConfigName && !(0,model_config/* getModelConfig */.Ac)(modelConfigName)) {
22239
+ // Missing or disabled configs fall through to routing / the default; a
22240
+ // disabled agent override is kept so re-enabling the model restores it.
22241
+ if (modelConfigName && !(0,model_config/* isModelConfigEnabled */.FO)((0,model_config/* getModelConfig */.Ac)(modelConfigName))) {
22238
22242
  modelConfigName = null;
22239
22243
  }
22240
22244
  let routeDecision = null;
@@ -22263,13 +22267,14 @@ function appendHistoryMessage(history, injected) {
22263
22267
  if (!modelConfigName && useRouter) {
22264
22268
  const pricingTables = (0,pricing/* getPricingTables */.iH)();
22265
22269
  const routed = (0,model_router/* routeTurnModel */.St)({
22266
- models: (0,model_config/* listModelConfigs */.D1)().map((cfg)=>({
22270
+ models: (0,model_config/* listEnabledModelConfigs */["if"])().map((cfg)=>({
22267
22271
  name: cfg.name,
22268
22272
  provider: cfg.provider,
22269
22273
  model_id: cfg.model_id,
22270
22274
  params: (0,model_config/* getModelParams */.mN)(cfg),
22271
22275
  credential_id: cfg.credential_id ?? null,
22272
22276
  is_default: cfg.is_default === 1,
22277
+ enabled: true,
22273
22278
  created_at: cfg.created_at,
22274
22279
  updated_at: cfg.updated_at
22275
22280
  })),
@@ -392,7 +392,7 @@ var lazy_json = __webpack_require__(62112);
392
392
  // EXTERNAL MODULE: ./node_modules/@smithy/core/dist-es/submodules/serde/index.js + 2 modules
393
393
  var serde = __webpack_require__(34538);
394
394
  // EXTERNAL MODULE: ./node_modules/@smithy/core/dist-es/submodules/protocols/SerdeContext.js
395
- var SerdeContext = __webpack_require__(49585);
395
+ var SerdeContext = __webpack_require__(71966);
396
396
  // EXTERNAL MODULE: ./node_modules/@smithy/core/dist-es/submodules/protocols/serde/determineTimestampFormat.js
397
397
  var determineTimestampFormat = __webpack_require__(67055);
398
398
  ;// ./node_modules/@smithy/core/dist-es/submodules/protocols/serde/ToStringShapeSerializer.js
@@ -793,7 +793,7 @@ var NumericValue = __webpack_require__(3999);
793
793
  // EXTERNAL MODULE: ./node_modules/@smithy/core/dist-es/submodules/serde/util-utf8/toUtf8.js
794
794
  var toUtf8 = __webpack_require__(63332);
795
795
  // EXTERNAL MODULE: ./node_modules/@smithy/core/dist-es/submodules/protocols/SerdeContext.js
796
- var SerdeContext = __webpack_require__(49585);
796
+ var SerdeContext = __webpack_require__(71966);
797
797
  // EXTERNAL MODULE: ./node_modules/@smithy/core/dist-es/submodules/protocols/serde/determineTimestampFormat.js
798
798
  var determineTimestampFormat = __webpack_require__(67055);
799
799
  ;// ./node_modules/@smithy/core/dist-es/submodules/protocols/serde/FromStringShapeDeserializer.js
@@ -498,6 +498,7 @@ function runMigrations(db) {
498
498
  ensureCredentialsTable(db);
499
499
  ensureCredentialsLabelAndDefaultColumns(db);
500
500
  ensureModelConfigCredentialIdColumn(db);
501
+ ensureModelConfigEnabledColumn(db);
501
502
  ensureAgentConfigsToolCredentialsColumn(db);
502
503
  seedAgentConfigs(db);
503
504
  backfillDeveloperInteractiveTerminalTools(db);
@@ -1484,6 +1485,14 @@ function ensureModelConfigCredentialIdColumn(db) {
1484
1485
  db.exec("ALTER TABLE model_configs ADD COLUMN credential_id TEXT");
1485
1486
  }
1486
1487
  }
1488
+ // ADR-0102: disabled model configs stay saved but are hidden from model
1489
+ // selection, routing, and default fallback. Existing rows default to enabled.
1490
+ function ensureModelConfigEnabledColumn(db) {
1491
+ const cols = db.prepare("PRAGMA table_info(model_configs)").all();
1492
+ if (!cols.some((c)=>c.name === "enabled")) {
1493
+ db.exec("ALTER TABLE model_configs ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1");
1494
+ }
1495
+ }
1487
1496
  // Idempotent backfill: for each model_config still carrying an inline
1488
1497
  // `api_key` (and no `credential_id` yet), create a credential row and
1489
1498
  // link by id. Strips the api_key / base_url / extra_headers fields from
@@ -7315,17 +7324,17 @@ function pickGitHubCompatOptions(params) {
7315
7324
  return out;
7316
7325
  }
7317
7326
  const tokenCache = new Map();
7318
- function isLikelyGitHubPat(token) {
7319
- return token.startsWith("ghp_") || token.startsWith("github_pat_") || token.startsWith("gho_");
7327
+ function isGitHubPat(token) {
7328
+ return token.startsWith("ghp_") || token.startsWith("github_pat_");
7320
7329
  }
7321
- async function getCopilotToken(pat) {
7322
- if (!pat) throw new Error("GitHub Copilot: no API key configured for token exchange.");
7323
- const cached = tokenCache.get(pat);
7330
+ async function exchangeCopilotSessionToken(githubToken) {
7331
+ if (!githubToken) throw new Error("GitHub Copilot: no API key configured for token exchange.");
7332
+ const cached = tokenCache.get(githubToken);
7324
7333
  if (cached && cached.expiresAt - Date.now() > 60000) return cached.token;
7325
- tokenCache.delete(pat);
7334
+ tokenCache.delete(githubToken);
7326
7335
  const res = await fetch("https://api.github.com/copilot_internal/v2/token", {
7327
7336
  headers: {
7328
- Authorization: `token ${pat}`,
7337
+ Authorization: `token ${githubToken}`,
7329
7338
  "User-Agent": "Jarela/1.0"
7330
7339
  },
7331
7340
  signal: AbortSignal.timeout(30000)
@@ -7333,17 +7342,17 @@ async function getCopilotToken(pat) {
7333
7342
  if (!res.ok) {
7334
7343
  const body = await (0,error/* readErrorBody */.N)(res);
7335
7344
  if (res.status === 401) {
7336
- throw new Error(`GitHub Copilot: token exchange failed (401). ` + `Ensure your PAT has the "copilot" scope and that you have an active Copilot subscription. ` + `Details: ${body}`);
7345
+ throw new Error(`GitHub Copilot: token exchange failed (401). ` + `Check that this GitHub account has an active Copilot subscription and the credential is valid. ` + `Details: ${body}`);
7337
7346
  }
7338
7347
  if (res.status === 404) {
7339
- throw new Error("GitHub Copilot token exchange endpoint was not found for this credential. " + "Personal Access Tokens are not accepted directly by Copilot chat. " + "Use a Copilot session token in params.copilot_session_token, or a credential that supports token exchange.");
7348
+ throw new Error("GitHub Copilot could not exchange this credential for a session token. " + "Use the in-app device-flow sign-in or set params.copilot_session_token.");
7340
7349
  }
7341
7350
  throw new Error(`GitHub Copilot: token exchange failed (${res.status}): ${body}`);
7342
7351
  }
7343
7352
  const json = await res.json();
7344
7353
  if (!json.token) throw new Error("GitHub Copilot: token exchange returned no token");
7345
7354
  const expiresAt = json.expires_at ? new Date(json.expires_at).getTime() : Date.now() + 28 * 60000;
7346
- tokenCache.set(pat, {
7355
+ tokenCache.set(githubToken, {
7347
7356
  token: json.token,
7348
7357
  expiresAt
7349
7358
  });
@@ -7374,7 +7383,6 @@ async function resolveCopilotAuth(params) {
7374
7383
  const explicitSessionToken = params.copilot_session_token?.trim();
7375
7384
  if (explicitSessionToken && explicitSessionToken.length > 0) {
7376
7385
  return {
7377
- kind: "copilot-api",
7378
7386
  baseURL: params.base_url ?? "https://api.githubcopilot.com",
7379
7387
  apiKey: explicitSessionToken,
7380
7388
  headers: {
@@ -7384,9 +7392,8 @@ async function resolveCopilotAuth(params) {
7384
7392
  }
7385
7393
  const oauthToken = (0,github_copilot_auth.getStoredOAuthToken)();
7386
7394
  if (oauthToken) {
7387
- const token = await getCopilotToken(oauthToken);
7395
+ const token = await exchangeCopilotSessionToken(oauthToken);
7388
7396
  return {
7389
- kind: "copilot-api",
7390
7397
  baseURL: params.base_url ?? "https://api.githubcopilot.com",
7391
7398
  apiKey: token,
7392
7399
  headers: {
@@ -7394,23 +7401,14 @@ async function resolveCopilotAuth(params) {
7394
7401
  }
7395
7402
  };
7396
7403
  }
7397
- if (apiKey && isLikelyGitHubPat(apiKey)) {
7398
- return {
7399
- kind: "models-rest",
7400
- baseURL: params.base_url ?? "https://models.github.ai/inference",
7401
- apiKey,
7402
- headers: {
7403
- Accept: "application/vnd.github+json",
7404
- "X-GitHub-Api-Version": "2026-03-10"
7405
- }
7406
- };
7404
+ if (apiKey && isGitHubPat(apiKey)) {
7405
+ throw new Error("GitHub Copilot: this provider's GitHub PAT route used GitHub Models, which has been retired. " + "Use the in-app device-flow sign-in or set copilot_session_token.");
7407
7406
  }
7408
7407
  if (!apiKey) {
7409
7408
  throw new Error("GitHub Copilot: not signed in. Use the in-app device-flow login, or set api_key / copilot_session_token.");
7410
7409
  }
7411
- const token = await getCopilotToken(apiKey);
7410
+ const token = await exchangeCopilotSessionToken(apiKey);
7412
7411
  return {
7413
- kind: "copilot-api",
7414
7412
  baseURL: params.base_url ?? "https://api.githubcopilot.com",
7415
7413
  apiKey: token,
7416
7414
  headers: {
@@ -7421,14 +7419,9 @@ async function resolveCopilotAuth(params) {
7421
7419
  // Anthropic SDK client pointed at Copilot's native `/v1/messages` endpoint.
7422
7420
  // Copilot accepts the Anthropic request shape (including `cache_control`
7423
7421
  // ephemeral breakpoints) for Claude-family models when called with a
7424
- // session token. The PAT-only `models.github.ai` fallback does NOT expose
7425
- // this endpoint, so we surface a clear error there instead of silently
7426
- // downgrading to a path that strips the cache markers.
7422
+ // Copilot session token.
7427
7423
  async function resolvedAnthropicClient(params) {
7428
7424
  const auth = await resolveCopilotAuth(params);
7429
- if (auth.kind !== "copilot-api") {
7430
- throw new Error("GitHub Copilot: Claude models require a Copilot session token (OAuth or copilot_session_token). " + "Personal Access Tokens route to GitHub Models REST, which does not expose the native Messages API.");
7431
- }
7432
7425
  return new client/* Anthropic */.NB({
7433
7426
  // The SDK requires an apiKey field; we authenticate via authToken
7434
7427
  // (Authorization: Bearer ...) per Copilot's contract. The empty
@@ -9481,14 +9474,14 @@ const INTEGRATIONS = {
9481
9474
  "github-copilot": {
9482
9475
  label: "GitHub Copilot",
9483
9476
  category: "llm",
9484
- description: "Routes chat-completion requests through your Copilot subscription. Paste a GitHub PAT " + "with `copilot` scope from github.com/settings/tokens, or run the device-flow sign-in in " + "the Models panel.",
9477
+ description: "Routes chat-completion requests through your Copilot subscription. Use the device-flow " + "sign-in in the Models panel; legacy GitHub PAT authentication is no longer supported.",
9485
9478
  fields: [
9486
9479
  {
9487
9480
  key: "api_key",
9488
- label: "GitHub PAT",
9489
- placeholder: "ghp_… or github_pat_…",
9481
+ label: "Legacy GitHub PAT (unsupported)",
9482
+ placeholder: "Use Models panel sign-in",
9490
9483
  secret: true,
9491
- required: true
9484
+ required: false
9492
9485
  }
9493
9486
  ]
9494
9487
  },
@@ -9758,6 +9751,7 @@ function saveIntegration(name, incoming) {
9758
9751
  if (f.required && !v.trim()) return {
9759
9752
  error: `"${f.key}" cannot be empty`
9760
9753
  };
9754
+ if (!v.trim()) continue;
9761
9755
  merged[f.key] = v;
9762
9756
  if (existing[f.key] !== v) touched.push(f.key);
9763
9757
  }
@@ -9904,11 +9898,17 @@ function isRetryableProviderStreamError(msg) {
9904
9898
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9905
9899
  /* harmony export */ Ac: () => (/* binding */ getModelConfig),
9906
9900
  /* harmony export */ D1: () => (/* binding */ listModelConfigs),
9901
+ /* harmony export */ FO: () => (/* binding */ isModelConfigEnabled),
9907
9902
  /* harmony export */ JI: () => (/* binding */ deleteModelConfig),
9903
+ /* harmony export */ LZ: () => (/* binding */ ModelConfigStateError),
9908
9904
  /* harmony export */ TN: () => (/* binding */ upsertModelConfig),
9905
+ /* harmony export */ Z1: () => (/* binding */ setModelConfigEnabled),
9906
+ /* harmony export */ dj: () => (/* binding */ setDefaultModelConfig),
9909
9907
  /* harmony export */ h5: () => (/* binding */ withModelConfigMutation),
9908
+ /* harmony export */ "if": () => (/* binding */ listEnabledModelConfigs),
9910
9909
  /* harmony export */ lB: () => (/* binding */ getDefaultModelConfig),
9911
- /* harmony export */ mN: () => (/* binding */ getModelParams)
9910
+ /* harmony export */ mN: () => (/* binding */ getModelParams),
9911
+ /* harmony export */ np: () => (/* binding */ resolveRunnableModelConfig)
9912
9912
  /* harmony export */ });
9913
9913
  /* harmony import */ var _lib_db__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(46);
9914
9914
  /* harmony import */ var _lib_crypto_envelope__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(10938);
@@ -9924,6 +9924,14 @@ const now = ()=>new Date().toISOString();
9924
9924
  function withModelConfigMutation(name, operation) {
9925
9925
  return (0,_lib_utils_keyed_mutation__WEBPACK_IMPORTED_MODULE_4__/* .withKeyedMutation */ .m)(`model-config:${name}`, operation);
9926
9926
  }
9927
+ class ModelConfigStateError extends Error {
9928
+ constructor(message, status){
9929
+ super(message), this.status = status;
9930
+ }
9931
+ }
9932
+ function isModelConfigEnabled(cfg) {
9933
+ return !!cfg && cfg.enabled !== 0;
9934
+ }
9927
9935
  // `params` holds the provider API key alongside hyperparameters and is
9928
9936
  // stored encrypted at rest (ADR-0005). Decrypt before returning rows.
9929
9937
  function decryptRow(row) {
@@ -9936,18 +9944,74 @@ function listModelConfigs() {
9936
9944
  const rows = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)().prepare("SELECT * FROM model_configs ORDER BY is_default DESC, name ASC").all();
9937
9945
  return rows.map(decryptRow);
9938
9946
  }
9947
+ /** Model configs that may be offered for selection or routing. */ function listEnabledModelConfigs() {
9948
+ return listModelConfigs().filter(isModelConfigEnabled);
9949
+ }
9939
9950
  function getModelConfig(name) {
9940
9951
  const row = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)().prepare("SELECT * FROM model_configs WHERE name=?").get(name) ?? null;
9941
9952
  return row ? decryptRow(row) : null;
9942
9953
  }
9943
9954
  function getDefaultModelConfig() {
9944
9955
  const db = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)();
9945
- const row = db.prepare("SELECT * FROM model_configs WHERE is_default=1 LIMIT 1").get() ?? null;
9956
+ const row = db.prepare("SELECT * FROM model_configs WHERE is_default=1 AND enabled=1 LIMIT 1").get() ?? null;
9946
9957
  if (row) return decryptRow(row);
9947
- const fallback = db.prepare("SELECT * FROM model_configs ORDER BY name ASC LIMIT 1").get() ?? null;
9958
+ const fallback = db.prepare("SELECT * FROM model_configs WHERE enabled=1 ORDER BY name ASC LIMIT 1").get() ?? null;
9948
9959
  return fallback ? decryptRow(fallback) : null;
9949
9960
  }
9950
- function upsertModelConfig(name, provider, model_id, params, is_default, credential_id = null) {
9961
+ /**
9962
+ * Resolve the config a run should use: the named config when it exists and is
9963
+ * enabled, otherwise the workspace default. A disabled assignment therefore
9964
+ * falls back without being cleared, so re-enabling restores it.
9965
+ */ function resolveRunnableModelConfig(name) {
9966
+ const named = name ? getModelConfig(name) : null;
9967
+ if (named && isModelConfigEnabled(named)) return named;
9968
+ return getDefaultModelConfig();
9969
+ }
9970
+ /** Atomically make `name` the only default model config. */ function setDefaultModelConfig(name) {
9971
+ return (0,_lib_db_transaction__WEBPACK_IMPORTED_MODULE_3__/* .withDbTransaction */ .J)(()=>{
9972
+ const db = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)();
9973
+ const target = getModelConfig(name);
9974
+ if (!target) throw new ModelConfigStateError(`Model config "${name}" not found`, 404);
9975
+ if (!isModelConfigEnabled(target)) {
9976
+ throw new ModelConfigStateError(`Model config "${name}" is disabled; enable it before making it the default`, 409);
9977
+ }
9978
+ const t = now();
9979
+ db.prepare("UPDATE model_configs SET is_default=0, updated_at=? WHERE is_default=1 AND name<>?").run(t, name);
9980
+ db.prepare("UPDATE model_configs SET is_default=1, updated_at=? WHERE name=?").run(t, name);
9981
+ return getModelConfig(name);
9982
+ });
9983
+ }
9984
+ /**
9985
+ * Enable or disable a model config. Disabling the default promotes the
9986
+ * alphabetically-first other enabled config in the same transaction, and the
9987
+ * last enabled config cannot be disabled so runs always have a model.
9988
+ */ function setModelConfigEnabled(name, enabled) {
9989
+ return (0,_lib_db_transaction__WEBPACK_IMPORTED_MODULE_3__/* .withDbTransaction */ .J)(()=>{
9990
+ const db = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)();
9991
+ const target = getModelConfig(name);
9992
+ if (!target) throw new ModelConfigStateError(`Model config "${name}" not found`, 404);
9993
+ if (isModelConfigEnabled(target) === enabled) return target;
9994
+ const t = now();
9995
+ if (enabled) {
9996
+ db.prepare("UPDATE model_configs SET enabled=1, updated_at=? WHERE name=?").run(t, name);
9997
+ return getModelConfig(name);
9998
+ }
9999
+ const next = db.prepare("SELECT name FROM model_configs WHERE enabled=1 AND name<>? ORDER BY name ASC LIMIT 1").get(name);
10000
+ if (!next?.name) {
10001
+ throw new ModelConfigStateError("At least one model config must stay enabled", 409);
10002
+ }
10003
+ db.prepare("UPDATE model_configs SET enabled=0, is_default=0, updated_at=? WHERE name=?").run(t, name);
10004
+ if (target.is_default === 1) {
10005
+ db.prepare("UPDATE model_configs SET is_default=1, updated_at=? WHERE name=?").run(t, next.name);
10006
+ }
10007
+ return getModelConfig(name);
10008
+ });
10009
+ }
10010
+ /**
10011
+ * Insert or update a model config. `is_default` undefined keeps the current
10012
+ * flag (false for new rows); true atomically clears every other default and
10013
+ * re-enables the row, because the default must always be selectable.
10014
+ */ function upsertModelConfig(name, provider, model_id, params, is_default, credential_id = null) {
9951
10015
  return (0,_lib_db_transaction__WEBPACK_IMPORTED_MODULE_3__/* .withDbTransaction */ .J)(()=>{
9952
10016
  const t = now();
9953
10017
  const existing = getModelConfig(name);
@@ -9963,9 +10027,11 @@ function upsertModelConfig(name, provider, model_id, params, is_default, credent
9963
10027
  ...decodeInlineParams(existing),
9964
10028
  ...params
9965
10029
  };
10030
+ const finalDefault = is_default ?? existing?.is_default === 1;
10031
+ const finalEnabled = finalDefault || !existing || isModelConfigEnabled(existing);
9966
10032
  const db = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .Lf)();
9967
- if (is_default) db.prepare("UPDATE model_configs SET is_default=0").run();
9968
- db.prepare("INSERT OR REPLACE INTO model_configs (name,provider,model_id,params,is_default,credential_id,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(name, provider, model_id, (0,_lib_crypto_envelope__WEBPACK_IMPORTED_MODULE_1__/* .encrypt */ .w)(JSON.stringify(mergedParams)), is_default ? 1 : 0, finalCredId, created_at, t);
10033
+ if (finalDefault) db.prepare("UPDATE model_configs SET is_default=0 WHERE name<>?").run(name);
10034
+ db.prepare("INSERT OR REPLACE INTO model_configs (name,provider,model_id,params,is_default,enabled,credential_id,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?)").run(name, provider, model_id, (0,_lib_crypto_envelope__WEBPACK_IMPORTED_MODULE_1__/* .encrypt */ .w)(JSON.stringify(mergedParams)), finalDefault ? 1 : 0, finalEnabled ? 1 : 0, finalCredId, created_at, t);
9969
10035
  return getModelConfig(name);
9970
10036
  });
9971
10037
  }
@@ -9978,10 +10044,10 @@ function deleteModelConfig(name) {
9978
10044
  // Automatically reset any agent pointing to this deleted model config to NULL (automatic / default model)
9979
10045
  db.prepare("UPDATE agent_configs SET model_config_name = NULL WHERE model_config_name = ?").run(name);
9980
10046
  // Avoid the "no default" state: if the deleted row was the default and any
9981
- // other rows remain, promote the alphabetically-first remaining row so
10047
+ // other enabled rows remain, promote the alphabetically-first one so
9982
10048
  // agents that fall back to the default keep working.
9983
10049
  if (wasDefault) {
9984
- const next = db.prepare("SELECT name FROM model_configs ORDER BY name ASC LIMIT 1").get();
10050
+ const next = db.prepare("SELECT name FROM model_configs WHERE enabled=1 ORDER BY name ASC LIMIT 1").get();
9985
10051
  if (next?.name) db.prepare("UPDATE model_configs SET is_default=1 WHERE name=?").run(next.name);
9986
10052
  }
9987
10053
  return true;
@@ -10482,9 +10548,8 @@ function isStructuredMemoryEligible(record, policy) {
10482
10548
  /* unused harmony export storeOAuthToken */
10483
10549
  /* harmony import */ var _lib_stores_memory__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(80956);
10484
10550
  /* harmony import */ var _lib_utils_error__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(22032);
10485
- // GitHub Copilot device-flow login. Lets users sign in with their Copilot
10486
- // subscription instead of pasting a PAT (which can't be exchanged for a
10487
- // Copilot session token and gets rate-limited on the Models REST API).
10551
+ // GitHub Copilot device-flow login. The legacy PAT path depended on GitHub
10552
+ // Models, which has been retired; use this flow for Copilot Chat access.
10488
10553
  //
10489
10554
  // Flow (RFC 8628):
10490
10555
  // 1. POST github.com/login/device/code with the VS Code Copilot client_id.
@@ -124,6 +124,54 @@ exports.modules = {
124
124
  }
125
125
 
126
126
 
127
+ /***/ }),
128
+
129
+ /***/ 3872:
130
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
131
+
132
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
133
+ /* harmony export */ Fu: () => (/* binding */ resolveAgentModel),
134
+ /* harmony export */ b_: () => (/* binding */ isModelEnabled),
135
+ /* harmony export */ xu: () => (/* binding */ agentModelStatus)
136
+ /* harmony export */ });
137
+ /**
138
+ * Resolve which `ModelConfig` will actually be used for an agent at runtime.
139
+ *
140
+ * Mirrors what `lib/agents/llm.ts` does server-side: when an agent has an
141
+ * enabled `model_config_name`, that wins; otherwise the workspace-wide
142
+ * default among enabled models is used. Both client (BridgeEditor, AgentEditor capability hints)
143
+ * and server need the same logic — this is the single source of truth.
144
+ *
145
+ * Returns `null` only when neither the requested config nor the default
146
+ * exists in the provided list (typically: no models configured yet).
147
+ */ function resolveAgentModel(agent, models) {
148
+ const enabled = models.filter(isModelEnabled);
149
+ const defaultModel = enabled.find((m)=>m.is_default) ?? enabled[0] ?? null;
150
+ if (!agent?.model_config_name) return defaultModel;
151
+ return enabled.find((m)=>m.name === agent.model_config_name) ?? defaultModel;
152
+ }
153
+ /** Disabled configs stay stored but are skipped by runtime resolution. */ function isModelEnabled(model) {
154
+ return model.enabled !== false;
155
+ }
156
+ /**
157
+ * Diagnose whether an agent has a runnable model RIGHT NOW. Used by the
158
+ * Scheduled Tasks and Watchers panels to surface model-availability issues
159
+ * before an unattended run silently no-ops with `no_model`.
160
+ */ function agentModelStatus(agent, models) {
161
+ if (!agent) return {
162
+ state: "no-agent"
163
+ };
164
+ const effectiveModel = resolveAgentModel(agent, models);
165
+ if (effectiveModel) return {
166
+ state: "ok",
167
+ model: effectiveModel
168
+ };
169
+ return {
170
+ state: "no-model"
171
+ };
172
+ }
173
+
174
+
127
175
  /***/ }),
128
176
 
129
177
  /***/ 4990:
@@ -707,6 +755,11 @@ async function request(path, init) {
707
755
  "Content-Type": "application/json",
708
756
  ...fetchInit.headers
709
757
  },
758
+ // The in-memory list/value caches above are the client cache layer
759
+ // and are invalidated on writes. The browser HTTP cache would honor
760
+ // the server's short max-age and serve a stale list right after a
761
+ // save, so bypass it unless a caller opts in.
762
+ cache: "no-store",
710
763
  ...fetchInit,
711
764
  signal: timeoutCtrl.signal
712
765
  });
@@ -1120,6 +1173,17 @@ const api = {
1120
1173
  if (false) {}
1121
1174
  return updated;
1122
1175
  },
1176
+ patch: async (name, data)=>{
1177
+ const updated = await request(`/models/${encodeURIComponent(name)}`, {
1178
+ method: "PATCH",
1179
+ body: JSON.stringify(data)
1180
+ });
1181
+ // Enabling/disabling or setting the default can move the default flag
1182
+ // on other rows, so drop the cached list instead of patching one row.
1183
+ invalidateModelListCache();
1184
+ if (false) {}
1185
+ return updated;
1186
+ },
1123
1187
  delete: async (name)=>{
1124
1188
  const res = await request(`/models/${encodeURIComponent(name)}`, {
1125
1189
  method: "DELETE"
@@ -1239,7 +1303,10 @@ const api = {
1239
1303
  }
1240
1304
  },
1241
1305
  dashboard: {
1242
- metrics: (days = 30)=>request(`/dashboard/metrics?days=${encodeURIComponent(String(days))}`),
1306
+ // Read-only aggregates: the server's 30s HTTP cache is intended here.
1307
+ metrics: (days = 30)=>request(`/dashboard/metrics?days=${encodeURIComponent(String(days))}`, {
1308
+ cache: "default"
1309
+ }),
1243
1310
  pricingTask: ()=>request("/dashboard/pricing-task"),
1244
1311
  setupPricingTask: ()=>request("/dashboard/pricing-task", {
1245
1312
  method: "POST",
@@ -2669,6 +2736,8 @@ function IconPicker({ form }) {
2669
2736
  });
2670
2737
  }
2671
2738
 
2739
+ // EXTERNAL MODULE: ./lib/agents/effective-model.ts
2740
+ var effective_model = __webpack_require__(3872);
2672
2741
  // EXTERNAL MODULE: ./lib/providers/capabilities.ts
2673
2742
  var capabilities = __webpack_require__(46069);
2674
2743
  // EXTERNAL MODULE: ./components/models/CapBadges.tsx
@@ -2688,10 +2757,13 @@ var feature_readiness = __webpack_require__(34974);
2688
2757
 
2689
2758
 
2690
2759
 
2760
+
2691
2761
  function ModelSection({ form, models, integrations, onClose }) {
2692
2762
  const { dispatch } = (0,AppContext/* useAppContext */.U)();
2693
- const defaultModel = models.find((m)=>m.is_default);
2694
- const selectedModel = models.find((m)=>m.name === form.modelConfigName) ?? defaultModel;
2763
+ const enabledModels = models.filter(effective_model/* isModelEnabled */.b_);
2764
+ const defaultModel = enabledModels.find((m)=>m.is_default) ?? enabledModels[0];
2765
+ const forcedModel = enabledModels.find((m)=>m.name === form.modelConfigName);
2766
+ const selectedModel = forcedModel ?? defaultModel;
2695
2767
  const readiness = (0,feature_readiness/* computeFeatureReadiness */.s)({
2696
2768
  models,
2697
2769
  integrations,
@@ -2746,7 +2818,7 @@ function ModelSection({ form, models, integrations, onClose }) {
2746
2818
  className: "text-[11px] text-amber-700 dark:text-amber-400",
2747
2819
  children: "No default fallback model is configured yet."
2748
2820
  }),
2749
- form.modelConfigName && models.find((m)=>m.name === form.modelConfigName) && /*#__PURE__*/ (0,react_jsx_runtime.jsx)("p", {
2821
+ forcedModel && /*#__PURE__*/ (0,react_jsx_runtime.jsx)("p", {
2750
2822
  className: "text-[11px] text-sky-700 dark:text-sky-400",
2751
2823
  children: "This agent currently has a forced model override. Clear it in Advanced settings to return to router-driven selection."
2752
2824
  })
@@ -4485,6 +4557,7 @@ function ToolCredentialRow({ tool, options, selectedId, onChange }) {
4485
4557
 
4486
4558
 
4487
4559
 
4560
+
4488
4561
  const Divider = ()=>/*#__PURE__*/ (0,react_jsx_runtime.jsx)("hr", {
4489
4562
  className: "border-border/60 my-2"
4490
4563
  });
@@ -4509,7 +4582,7 @@ function AdvancedSection({ form, models, integrations, isFullMode, onClose }) {
4509
4582
  value: "",
4510
4583
  children: "Automatic routing (recommended)"
4511
4584
  }),
4512
- models.map((m)=>{
4585
+ models.filter((m)=>(0,effective_model/* isModelEnabled */.b_)(m) || m.name === form.modelConfigName).map((m)=>{
4513
4586
  const vision = (0,capabilities/* modelSupportsImages */.Q5)(m.provider, m.model_id);
4514
4587
  return /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("option", {
4515
4588
  value: m.name,
@@ -4519,12 +4592,17 @@ function AdvancedSection({ form, models, integrations, isFullMode, onClose }) {
4519
4592
  " \xb7 ",
4520
4593
  m.provider,
4521
4594
  " / ",
4522
- m.model_id
4595
+ m.model_id,
4596
+ (0,effective_model/* isModelEnabled */.b_)(m) ? "" : " (disabled)"
4523
4597
  ]
4524
4598
  }, m.name);
4525
4599
  })
4526
4600
  ]
4527
4601
  }),
4602
+ selectedModel && !(0,effective_model/* isModelEnabled */.b_)(selectedModel) && /*#__PURE__*/ (0,react_jsx_runtime.jsx)("p", {
4603
+ className: "text-[11px] text-amber-700 dark:text-amber-400",
4604
+ children: "This model is disabled, so the agent uses automatic routing until it is enabled again."
4605
+ }),
4528
4606
  selectedModel && /*#__PURE__*/ (0,react_jsx_runtime.jsx)(SelectedModelDescription, {
4529
4607
  model: selectedModel,
4530
4608
  prefix: "Forced"
@@ -5698,7 +5776,7 @@ function StepCredentials({ definitions, integrations, onChanged }) {
5698
5776
  }),
5699
5777
  eyebrow: "Step 2 \xb7 Credentials",
5700
5778
  title: "Connect provider credentials in the UI first",
5701
- description: "Use the built-in credential forms as the main setup path. They work for Claude, GPT, Gemini, Copilot, and other providers without depending on your shell environment.",
5779
+ description: "Use the built-in credential forms as the main setup path for Claude, GPT, Gemini, and other providers. GitHub Copilot uses device-flow sign-in in the Models panel.",
5702
5780
  children: [
5703
5781
  /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("div", {
5704
5782
  className: "rounded-xl border border-accent/30 bg-accent/5 px-3 py-2.5 text-xs text-fg",
@@ -5902,10 +5980,7 @@ function StepModel({ models, onChanged }) {
5902
5980
  }
5903
5981
  async function handleSetDefault(m) {
5904
5982
  try {
5905
- await client/* api */.FH.models.update(m.name, {
5906
- provider: m.provider,
5907
- model_id: m.model_id,
5908
- params: m.params,
5983
+ await client/* api */.FH.models.patch(m.name, {
5909
5984
  is_default: true
5910
5985
  });
5911
5986
  onChanged();
@@ -6040,7 +6115,7 @@ function StepModel({ models, onChanged }) {
6040
6115
  /*#__PURE__*/ (0,react_jsx_runtime.jsxs)("div", {
6041
6116
  className: "flex shrink-0 items-center gap-0.5",
6042
6117
  children: [
6043
- !m.is_default && /*#__PURE__*/ (0,react_jsx_runtime.jsx)("button", {
6118
+ !m.is_default && m.enabled !== false && /*#__PURE__*/ (0,react_jsx_runtime.jsx)("button", {
6044
6119
  type: "button",
6045
6120
  onClick: (e)=>{
6046
6121
  e.stopPropagation();
@@ -8796,7 +8871,7 @@ function AuthHeader({ status, polling, onSignIn, onSignOut }) {
8796
8871
  }),
8797
8872
  /*#__PURE__*/ (0,react_jsx_runtime.jsx)("p", {
8798
8873
  className: "text-fg-faint mt-0.5",
8799
- children: "Unlocks full model context windows (vs. the 8k cap on raw PATs via GitHub Models)."
8874
+ children: "Required for Copilot models; GitHub PAT authentication is no longer supported."
8800
8875
  })
8801
8876
  ]
8802
8877
  }),
@@ -2165,7 +2165,7 @@ var NumericValue = __webpack_require__(3999);
2165
2165
  // EXTERNAL MODULE: ./node_modules/@smithy/core/dist-es/submodules/serde/util-utf8/toUtf8.js
2166
2166
  var toUtf8 = __webpack_require__(63332);
2167
2167
  // EXTERNAL MODULE: ./node_modules/@smithy/core/dist-es/submodules/protocols/SerdeContext.js
2168
- var SerdeContext = __webpack_require__(49585);
2168
+ var SerdeContext = __webpack_require__(71966);
2169
2169
  // EXTERNAL MODULE: ./node_modules/@smithy/core/dist-es/submodules/protocols/serde/determineTimestampFormat.js
2170
2170
  var determineTimestampFormat = __webpack_require__(67055);
2171
2171
  ;// ./node_modules/@smithy/core/dist-es/submodules/protocols/serde/FromStringShapeDeserializer.js