@moxxy/cli 0.9.0 → 0.10.0

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/dist/bin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { z as z$1, defineProvider, definePlugin, defineTool, MoxxyError, writeFileAtomic, asTurnId, defineMode, asPluginId, defineChannel, defineTunnelProvider, spawnCliTunnel, isCliTunnelAvailable, createMutex, defineWorkflowExecutor, toFriendlyError, estimateTextTokens, classifyHttpStatus, createStuckLoopDetector, runCompactionIfNeeded, runElisionIfNeeded, collectProviderStream, usageEventFields, isContextOverflowError, emitRequestsAndDetectStuck, executeToolUses, buildSystemPromptWithSkills, projectMessages, defineCompactor, defineCacheStrategy, denyByDefaultResolver, createAllowListResolver, moxxyPath, zodToJsonSchema, runSingleShotTurn, bearerTokenMatches, resolveChannelToken, rotateChannelToken, estimateContextTokens as estimateContextTokens$1, readRequestBody, MOXXY_WS_SUBPROTOCOL, defineEmbedder, bearerGuard, tokenFromWsProtocolHeader, skillFrontmatterSchema, asSkillId, getInstallHint, moxxyHome, defineTranscriber, summarizeTokensByModel, migrateModeName, createDeferredPermissionResolver, classifyNetworkError, addModelTotals, ISOLATION_RANK, moxxyPackageSchema, defineCommand, createCallbackResolver, autoAllowResolver, asSessionId, asToolCallId, defineViewRenderer, DEFAULT_VIEW_TAGS, isSafeViewUrl, evaluateToolRule, summarizeSessionTokensFromEvents, computeElisionState, toolResultStubbed, toolResultStub, toolResultBytes, conversationalStubbed, conversationalStub, asEventId } from '@moxxy/sdk';
3
+ import { z as z$1, defineProvider, definePlugin, defineTool, MoxxyError, writeFileAtomic, asTurnId, defineMode, asPluginId, defineChannel, defineTunnelProvider, spawnCliTunnel, isCliTunnelAvailable, createMutex, defineWorkflowExecutor, toFriendlyError, estimateTextTokens, classifyHttpStatus, createStuckLoopDetector, runCompactionIfNeeded, runElisionIfNeeded, collectProviderStream, usageEventFields, isContextOverflowError, emitRequestsAndDetectStuck, executeToolUses, buildSystemPromptWithSkills, projectMessages, defineCompactor, defineCacheStrategy, denyByDefaultResolver, createAllowListResolver, moxxyPath, zodToJsonSchema, runSingleShotTurn, bearerTokenMatches, resolveChannelToken, rotateChannelToken, estimateContextTokens as estimateContextTokens$1, readRequestBody, MOXXY_WS_SUBPROTOCOL, defineEmbedder, migrateModeName, bearerGuard, tokenFromWsProtocolHeader, skillFrontmatterSchema, asSkillId, getInstallHint, moxxyHome, defineTranscriber, summarizeTokensByModel, createDeferredPermissionResolver, classifyNetworkError, addModelTotals, ISOLATION_RANK, moxxyPackageSchema, defineCommand, createCallbackResolver, autoAllowResolver, asSessionId, asToolCallId, defineViewRenderer, DEFAULT_VIEW_TAGS, isSafeViewUrl, evaluateToolRule, summarizeSessionTokensFromEvents, computeElisionState, toolResultStubbed, toolResultStub, toolResultBytes, conversationalStubbed, conversationalStub, asEventId } from '@moxxy/sdk';
4
4
  import * as fs27 from 'fs';
5
5
  import fs27__default, { existsSync, promises, ReadStream, readFileSync, statSync, readdirSync, mkdirSync, writeFileSync, unlinkSync, renameSync, watch, createReadStream } from 'fs';
6
6
  import * as path3 from 'path';
@@ -1376,6 +1376,13 @@ var init_providers = __esm({
1376
1376
  defs = /* @__PURE__ */ new Map();
1377
1377
  instances = /* @__PURE__ */ new Map();
1378
1378
  active = null;
1379
+ /**
1380
+ * Names the user disabled. Kept name-based (not def-based) so it can be
1381
+ * seeded from preferences BEFORE the plugins register their defs — boot
1382
+ * order doesn't matter. A disabled provider stays registered/listable but
1383
+ * can't be activated.
1384
+ */
1385
+ disabled = /* @__PURE__ */ new Set();
1379
1386
  /**
1380
1387
  * Register a provider def. Throws on duplicate — use `replace()` for
1381
1388
  * explicit overwrite. Matches the semantics of `tools` and `channels`.
@@ -1404,10 +1411,32 @@ var init_providers = __esm({
1404
1411
  list() {
1405
1412
  return [...this.defs.values()];
1406
1413
  }
1414
+ /**
1415
+ * Enable/disable a provider by name. Disabling the ACTIVE provider is
1416
+ * refused — switch first, then disable — so a session never ends up with an
1417
+ * active-but-disabled provider. Unknown names are accepted (the set seeds
1418
+ * from preferences before plugins register), except when disabling via a
1419
+ * live toggle is meaningless because the provider is active.
1420
+ */
1421
+ setEnabled(name, enabled) {
1422
+ if (!enabled && this.active === name) {
1423
+ throw new Error(`Cannot disable the active provider "${name}" \u2014 switch providers first.`);
1424
+ }
1425
+ if (enabled)
1426
+ this.disabled.delete(name);
1427
+ else
1428
+ this.disabled.add(name);
1429
+ }
1430
+ isEnabled(name) {
1431
+ return !this.disabled.has(name);
1432
+ }
1407
1433
  setActive(name, config) {
1408
1434
  const def = this.defs.get(name);
1409
1435
  if (!def)
1410
1436
  throw new Error(`Provider not registered: ${name}`);
1437
+ if (this.disabled.has(name)) {
1438
+ throw new Error(`Provider "${name}" is disabled \u2014 enable it first.`);
1439
+ }
1411
1440
  let instance = this.instances.get(name);
1412
1441
  if (!instance) {
1413
1442
  instance = def.createClient(config ?? {});
@@ -3081,6 +3110,7 @@ var init_session = __esm({
3081
3110
  readyProviders;
3082
3111
  credentialResolver;
3083
3112
  mcpAdmin;
3113
+ providerAdmin;
3084
3114
  workflows;
3085
3115
  pluginsAdmin;
3086
3116
  dispatcher;
@@ -3262,7 +3292,8 @@ var init_session = __esm({
3262
3292
  // are the ones the desktop's "Fetch live" affordance targets;
3263
3293
  // they advertise this via the provider-admin factory by
3264
3294
  // setting `supportsLiveModelDiscovery: true` on their def.
3265
- supportsLiveModelDiscovery: p3.supportsLiveModelDiscovery === true
3295
+ supportsLiveModelDiscovery: p3.supportsLiveModelDiscovery === true,
3296
+ enabled: this.providers.isEnabled(p3.name)
3266
3297
  })),
3267
3298
  activeMode,
3268
3299
  activeModeBadge,
@@ -127441,6 +127472,15 @@ function noteTemperatureUnsupported() {
127441
127472
  console.debug("[openai-codex] ProviderRequest.temperature is not supported by the Codex Responses backend (gpt-5 reasoning models reject sampling params); ignoring it.");
127442
127473
  }
127443
127474
  }
127475
+ var maxTokensNoteShown = false;
127476
+ function noteMaxTokensUnsupported() {
127477
+ if (maxTokensNoteShown)
127478
+ return;
127479
+ maxTokensNoteShown = true;
127480
+ if (process.env.MOXXY_DEBUG) {
127481
+ console.debug("[openai-codex] ProviderRequest.maxTokens is not supported by the Codex Responses backend (400 Unsupported parameter: max_output_tokens); ignoring it.");
127482
+ }
127483
+ }
127444
127484
  function toResponsesBody(req, opts = {}) {
127445
127485
  const instructions = extractSystemText(req.messages, req.system) || DEFAULT_INSTRUCTIONS;
127446
127486
  const body = {
@@ -127458,7 +127498,7 @@ function toResponsesBody(req, opts = {}) {
127458
127498
  if (opts.sessionHint)
127459
127499
  body.prompt_cache_key = opts.sessionHint;
127460
127500
  if (req.maxTokens !== void 0)
127461
- body.max_output_tokens = req.maxTokens;
127501
+ noteMaxTokensUnsupported();
127462
127502
  if (req.temperature !== void 0)
127463
127503
  noteTemperatureUnsupported();
127464
127504
  return body;
@@ -134757,7 +134797,7 @@ function isRunnerUp(socketPath = runnerSocketPath()) {
134757
134797
 
134758
134798
  // ../runner/dist/server.js
134759
134799
  init_dist();
134760
- var RUNNER_PROTOCOL_VERSION = 6;
134800
+ var RUNNER_PROTOCOL_VERSION = 7;
134761
134801
  var MIN_COMPATIBLE_PROTOCOL_VERSION = 1;
134762
134802
  var RunnerMethod = {
134763
134803
  /** client->server: handshake; returns the initial info snapshot. */
@@ -134782,6 +134822,12 @@ var RunnerMethod = {
134782
134822
  ModeSetActive: "mode.setActive",
134783
134823
  /** client->server: switch the active provider (server resolves credentials). */
134784
134824
  ProviderSetActive: "provider.setActive",
134825
+ /** client->server: enable/disable a provider (v7; persists to preferences). */
134826
+ ProviderSetEnabled: "provider.setEnabled",
134827
+ /** client->server: re-probe every provider's credentials → readyProviders (v7). */
134828
+ ProviderRefreshReady: "provider.refreshReady",
134829
+ /** client->server: patch a stored (runtime-registered) provider's config (v7). */
134830
+ ProviderConfigure: "provider.configure",
134785
134831
  /** client->server: persist an allow-always permission rule. */
134786
134832
  PermissionAddAllow: "permission.addAllow",
134787
134833
  /** client->server: run a registered slash command on the runner. */
@@ -134877,6 +134923,19 @@ var providerSetActiveParamsSchema = z.object({
134877
134923
  name: z.string(),
134878
134924
  config: z.record(z.unknown()).optional()
134879
134925
  });
134926
+ var providerSetEnabledParamsSchema = z.object({
134927
+ name: z.string().min(1),
134928
+ enabled: z.boolean()
134929
+ });
134930
+ var providerConfigureParamsSchema = z.object({
134931
+ name: z.string().min(1),
134932
+ patch: z.object({
134933
+ baseURL: z.string().url().optional(),
134934
+ defaultModel: z.string().min(1).optional(),
134935
+ envVar: z.string().regex(/^[A-Z][A-Z0-9_]*$/).optional(),
134936
+ models: z.array(z.object({ id: z.string().min(1), contextWindow: z.number() }).passthrough()).min(1).optional()
134937
+ })
134938
+ });
134880
134939
  var permissionAddAllowParamsSchema = z.object({
134881
134940
  name: z.string(),
134882
134941
  reason: z.string().optional()
@@ -134983,6 +135042,9 @@ var RunnerServer = class {
134983
135042
  peer.handle(RunnerMethod.SetResolver, (raw) => this.handleSetResolver(client, raw));
134984
135043
  peer.handle(RunnerMethod.ModeSetActive, (raw) => this.handleModeSetActive(raw));
134985
135044
  peer.handle(RunnerMethod.ProviderSetActive, (raw) => this.handleProviderSetActive(raw));
135045
+ peer.handle(RunnerMethod.ProviderSetEnabled, (raw) => this.handleProviderSetEnabled(raw));
135046
+ peer.handle(RunnerMethod.ProviderRefreshReady, () => this.handleProviderRefreshReady());
135047
+ peer.handle(RunnerMethod.ProviderConfigure, (raw) => this.handleProviderConfigure(raw));
134986
135048
  peer.handle(RunnerMethod.PermissionAddAllow, (raw) => this.handlePermissionAddAllow(raw));
134987
135049
  peer.handle(RunnerMethod.CommandRun, (raw) => this.handleCommandRun(raw));
134988
135050
  peer.handle(RunnerMethod.Transcribe, (raw) => this.handleTranscribe(raw));
@@ -135058,6 +135120,7 @@ var RunnerServer = class {
135058
135120
  turnId,
135059
135121
  ...error2 ? { error: error2 } : {}
135060
135122
  });
135123
+ this.broadcastInfo();
135061
135124
  }
135062
135125
  });
135063
135126
  return { turnId };
@@ -135122,6 +135185,54 @@ var RunnerServer = class {
135122
135185
  this.broadcastInfo();
135123
135186
  return {};
135124
135187
  }
135188
+ async handleProviderSetEnabled(raw) {
135189
+ const { name, enabled } = providerSetEnabledParamsSchema.parse(raw);
135190
+ if (!this.session.providers.list().some((p3) => p3.name === name)) {
135191
+ throw new Error(`Provider not registered: ${name}`);
135192
+ }
135193
+ this.session.providers.setEnabled(name, enabled);
135194
+ void (async () => {
135195
+ const prefs = await loadPreferences();
135196
+ const current = new Set(prefs.disabledProviders ?? []);
135197
+ if (enabled)
135198
+ current.delete(name);
135199
+ else
135200
+ current.add(name);
135201
+ await savePreferences({ disabledProviders: [...current] });
135202
+ })();
135203
+ this.broadcastInfo();
135204
+ return {};
135205
+ }
135206
+ async handleProviderRefreshReady() {
135207
+ const resolver2 = this.session.credentialResolver;
135208
+ if (resolver2) {
135209
+ const ready = /* @__PURE__ */ new Set();
135210
+ const active = this.session.providers.getActiveName();
135211
+ if (active)
135212
+ ready.add(active);
135213
+ for (const p3 of this.session.providers.list()) {
135214
+ if (ready.has(p3.name))
135215
+ continue;
135216
+ try {
135217
+ await resolver2(p3.name);
135218
+ ready.add(p3.name);
135219
+ } catch {
135220
+ }
135221
+ }
135222
+ this.session.readyProviders = ready;
135223
+ }
135224
+ this.broadcastInfo();
135225
+ return {};
135226
+ }
135227
+ async handleProviderConfigure(raw) {
135228
+ const { name, patch } = providerConfigureParamsSchema.parse(raw);
135229
+ const admin = this.session.providerAdmin;
135230
+ if (!admin)
135231
+ throw new Error("provider admin not supported on this runner");
135232
+ await admin.configure(name, patch);
135233
+ this.broadcastInfo();
135234
+ return {};
135235
+ }
135125
135236
  async handlePermissionAddAllow(raw) {
135126
135237
  const { name, reason } = permissionAddAllowParamsSchema.parse(raw);
135127
135238
  await this.session.permissions.addAllow({ name, ...reason ? { reason } : {} });
@@ -135397,6 +135508,7 @@ var RemoteSession = class {
135397
135508
  requirements;
135398
135509
  permissions;
135399
135510
  mcpAdmin;
135511
+ providerAdmin;
135400
135512
  workflows;
135401
135513
  /**
135402
135514
  * Turns that completed before their `runTurn` stream was registered. A fast
@@ -135410,6 +135522,8 @@ var RemoteSession = class {
135410
135522
  permissionResolver = null;
135411
135523
  approvalResolver = null;
135412
135524
  info = null;
135525
+ /** Subscribers to `info.changed` pushes (see {@link onInfoChanged}). */
135526
+ infoListeners = /* @__PURE__ */ new Set();
135413
135527
  /**
135414
135528
  * The protocol version the SERVER reported at attach. Defaults to our own
135415
135529
  * version until the handshake resolves. Version-specific client methods (the
@@ -135435,6 +135549,12 @@ var RemoteSession = class {
135435
135549
  });
135436
135550
  this.peer.on(RunnerNotification.InfoChanged, (params) => {
135437
135551
  this.info = params.info;
135552
+ for (const fn of this.infoListeners) {
135553
+ try {
135554
+ fn(this.info);
135555
+ } catch {
135556
+ }
135557
+ }
135438
135558
  });
135439
135559
  this.peer.on(RunnerNotification.ReplayStart, (params) => {
135440
135560
  const { fromSeq } = params;
@@ -135472,6 +135592,7 @@ var RemoteSession = class {
135472
135592
  this.requirements = { check: () => ({ ready: false, issues: [] }) };
135473
135593
  this.permissions = this.makePermissionsView();
135474
135594
  this.mcpAdmin = this.makeMcpAdminView();
135595
+ this.providerAdmin = this.makeProviderAdminView();
135475
135596
  this.workflows = this.makeWorkflowsView();
135476
135597
  }
135477
135598
  /**
@@ -135537,6 +135658,16 @@ var RemoteSession = class {
135537
135658
  getInfo() {
135538
135659
  return this.requireInfo();
135539
135660
  }
135661
+ /**
135662
+ * Subscribe to runner `info.changed` pushes (registry snapshot changes —
135663
+ * provider/mode/MCP/workflow mutations, including ones made by tools inside
135664
+ * a turn). Fires after the local `getInfo()` mirror has been updated, so a
135665
+ * listener can re-read it synchronously. Returns an unsubscribe fn.
135666
+ */
135667
+ onInfoChanged(fn) {
135668
+ this.infoListeners.add(fn);
135669
+ return () => this.infoListeners.delete(fn);
135670
+ }
135540
135671
  async *runTurn(prompt, opts = {}) {
135541
135672
  const result = await this.peer.request(RunnerMethod.RunTurn, {
135542
135673
  prompt,
@@ -135740,6 +135871,27 @@ var RemoteSession = class {
135740
135871
  detach: (name) => this.peer.request(RunnerMethod.McpDetach, { name })
135741
135872
  };
135742
135873
  }
135874
+ // Provider management (protocol v7): backs the desktop's interactive
135875
+ // Settings → Providers tab. Gated on the SERVER's reported version so a v7
135876
+ // client attached to an older runner (a desktop whose JS hot-update outran
135877
+ // its bundled CLI) gets a clear "update the CLI" error instead of a raw
135878
+ // method-not-found.
135879
+ makeProviderAdminView() {
135880
+ return {
135881
+ setEnabled: async (name, enabled) => {
135882
+ this.requireServerProtocol(7, "Enabling/disabling a provider");
135883
+ await this.peer.request(RunnerMethod.ProviderSetEnabled, { name, enabled });
135884
+ },
135885
+ refreshReady: async () => {
135886
+ this.requireServerProtocol(7, "Re-probing provider credentials");
135887
+ await this.peer.request(RunnerMethod.ProviderRefreshReady, {});
135888
+ },
135889
+ configure: async (name, patch) => {
135890
+ this.requireServerProtocol(7, "Configuring a provider");
135891
+ await this.peer.request(RunnerMethod.ProviderConfigure, { name, patch });
135892
+ }
135893
+ };
135894
+ }
135743
135895
  makeWorkflowsView() {
135744
135896
  return {
135745
135897
  list: () => this.peer.request(RunnerMethod.WorkflowList),
@@ -138037,6 +138189,51 @@ var testProviderInput = z$1.object({
138037
138189
  baseURL: z$1.string().url().describe("Vendor API base URL to probe, e.g. https://api.deepseek.com."),
138038
138190
  keyName: z$1.string().regex(/^[A-Z][A-Z0-9_]*$/).describe("NAME of the vault secret holding the API key (e.g. DEEPSEEK_API_KEY). The key is resolved from the vault inside the tool \u2014 never ask the user for the plaintext key and never pass one as a tool argument. Have them store it first: /vault set <NAME> <key>.")
138039
138191
  });
138192
+ function buildProviderAdminPluginWithApi(opts) {
138193
+ const { providerRegistry, configPath } = opts;
138194
+ const api = {
138195
+ configure: async (name, patch) => {
138196
+ const cfg = await readProvidersConfig(configPath);
138197
+ const entry = cfg.providers.find((p3) => p3.name === name);
138198
+ if (!entry) {
138199
+ throw new MoxxyError({
138200
+ code: "CONFIG_INVALID",
138201
+ message: `provider-admin: no stored provider named "${name}" \u2014 only runtime-registered (providers.json) providers are configurable; built-ins are code.`
138202
+ });
138203
+ }
138204
+ const next = {
138205
+ ...entry,
138206
+ ...patch.baseURL ? { baseURL: patch.baseURL } : {},
138207
+ ...patch.defaultModel ? { defaultModel: patch.defaultModel } : {},
138208
+ ...patch.envVar ? { envVar: patch.envVar } : {},
138209
+ ...patch.models && patch.models.length > 0 ? { models: patch.models } : {}
138210
+ };
138211
+ if (!next.models.some((m3) => m3.id === next.defaultModel)) {
138212
+ throw new MoxxyError({
138213
+ code: "CONFIG_INVALID",
138214
+ message: `provider-admin: defaultModel "${next.defaultModel}" is not in the models list (${next.models.map((m3) => m3.id).join(", ")}).`
138215
+ });
138216
+ }
138217
+ const def = buildProviderDef(next);
138218
+ const hadDef = providerRegistry.list().some((p3) => p3.name === name);
138219
+ if (hadDef)
138220
+ providerRegistry.replace(def);
138221
+ else
138222
+ providerRegistry.register(def);
138223
+ try {
138224
+ await upsertStoredProvider(next, configPath);
138225
+ } catch (err) {
138226
+ const prev = buildProviderDef(entry);
138227
+ if (hadDef)
138228
+ providerRegistry.replace(prev);
138229
+ else
138230
+ providerRegistry.unregister(name);
138231
+ throw err;
138232
+ }
138233
+ }
138234
+ };
138235
+ return { plugin: buildProviderAdminPlugin(opts), api };
138236
+ }
138040
138237
  function buildProviderAdminPlugin(opts) {
138041
138238
  const { providerRegistry, configPath } = opts;
138042
138239
  return definePlugin({
@@ -144027,20 +144224,25 @@ function formatCatalog(entries, emptyLabel) {
144027
144224
  }
144028
144225
  async function draftWorkflow(provider, model, intent, signal, opts = {}) {
144029
144226
  let accumulated = "";
144227
+ let truncated = false;
144228
+ const ceiling = provider.models.find((m3) => m3.id === model)?.maxOutputTokens;
144229
+ const budget = Math.min(opts.maxTokens ?? DEFAULT_MAX_TOKENS, ceiling ?? Number.POSITIVE_INFINITY);
144030
144230
  for await (const event of provider.stream({
144031
144231
  model,
144032
144232
  system: buildSystemPrompt(opts),
144033
144233
  messages: [{ role: "user", content: [{ type: "text", text: `Build a workflow for: ${intent}` }] }],
144034
- maxTokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,
144234
+ maxTokens: budget,
144035
144235
  signal
144036
144236
  })) {
144037
144237
  if (event.type === "text_delta")
144038
144238
  accumulated += event.delta;
144239
+ if (event.type === "message_end")
144240
+ truncated = event.stopReason === "max_tokens";
144039
144241
  if (event.type === "error")
144040
144242
  throw new Error(`workflow_create: provider error: ${event.message}`);
144041
144243
  }
144042
144244
  const raw = extractYamlBlock(accumulated);
144043
- return { raw, parse: parseWorkflowYaml(raw) };
144245
+ return { raw, parse: parseWorkflowYaml(raw), truncated };
144044
144246
  }
144045
144247
  function extractYamlBlock(s2) {
144046
144248
  const fence = /```(?:ya?ml)?\n([\s\S]*?)```/.exec(s2);
@@ -144119,7 +144321,7 @@ function createTool(deps) {
144119
144321
  if (!drafted.parse.ok || !drafted.parse.workflow) {
144120
144322
  throw new MoxxyError({
144121
144323
  code: "TOOL_ERROR",
144122
- message: `workflow_create: the model did not produce a valid workflow (${drafted.parse.errors.join("; ")}). Try a more specific intent.`
144324
+ message: drafted.truncated ? "workflow_create: the draft hit the output-token limit before the YAML was complete. Try a simpler intent, or split the workflow into smaller ones." : `workflow_create: the model did not produce a valid workflow (${drafted.parse.errors.join("; ")}). Try a more specific intent.`
144123
144325
  });
144124
144326
  }
144125
144327
  const created = await deps.store.create(drafted.parse.workflow, scope);
@@ -145292,10 +145494,11 @@ function buildBuiltinsCore(args) {
145292
145494
  // ~/.moxxy/providers.json; the plugin's onInit re-registers them on
145293
145495
  // every boot. Pairs with the `add-provider` skill which walks the
145294
145496
  // model through gathering baseURL + models + key.
145295
- {
145296
- name: "@moxxy/plugin-provider-admin",
145297
- plugin: buildProviderAdminPlugin({ providerRegistry: session.providers })
145298
- },
145497
+ (() => {
145498
+ const { plugin: plugin4, api } = buildProviderAdminPluginWithApi({ providerRegistry: session.providers });
145499
+ session.providerAdmin = api;
145500
+ return { name: "@moxxy/plugin-provider-admin", plugin: plugin4 };
145501
+ })(),
145299
145502
  // Admin tools (mcp_add_server, mcp_list_servers, mcp_remove_server,
145300
145503
  // mcp_test_server) plus the boot-time lazy attach. Passing the
145301
145504
  // session's live tool registry enables both hot-attach for runtime
@@ -145794,6 +145997,10 @@ async function activateProvider(args) {
145794
145997
  } else {
145795
145998
  for (let i2 = 0; i2 < candidates.length; i2++) {
145796
145999
  const candidate = candidates[i2];
146000
+ if (!session.providers.isEnabled(candidate)) {
146001
+ logger.warn("skipping disabled provider", { provider: candidate });
146002
+ continue;
146003
+ }
145797
146004
  const interactive = i2 === 0 && !skipKeyPrompt && process.stdin.isTTY === true;
145798
146005
  try {
145799
146006
  const resolved = await resolveProviderCredentials(candidate, vault, {
@@ -146002,6 +146209,13 @@ async function setupSessionWithConfig(opts) {
146002
146209
  });
146003
146210
  await selectEmbedder(session, rawConfig.embeddings, logger);
146004
146211
  for (const iso2 of session.isolators.list()) security.registry.register(iso2);
146212
+ try {
146213
+ const bootPrefs = await loadPreferences();
146214
+ for (const name of bootPrefs.disabledProviders ?? []) {
146215
+ session.providers.setEnabled(name, false);
146216
+ }
146217
+ } catch {
146218
+ }
146005
146219
  const { credentialResolver } = await activateProvider({
146006
146220
  session,
146007
146221
  config,