@f5xc-salesdemos/xcsh 17.0.2 → 17.1.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "17.0.2",
4
+ "version": "17.1.1",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5xc-salesdemos/xcsh",
7
7
  "author": "Can Boluk",
@@ -46,12 +46,12 @@
46
46
  "dependencies": {
47
47
  "@agentclientprotocol/sdk": "0.16.1",
48
48
  "@mozilla/readability": "^0.6",
49
- "@f5xc-salesdemos/xcsh-stats": "17.0.2",
50
- "@f5xc-salesdemos/pi-agent-core": "17.0.2",
51
- "@f5xc-salesdemos/pi-ai": "17.0.2",
52
- "@f5xc-salesdemos/pi-natives": "17.0.2",
53
- "@f5xc-salesdemos/pi-tui": "17.0.2",
54
- "@f5xc-salesdemos/pi-utils": "17.0.2",
49
+ "@f5xc-salesdemos/xcsh-stats": "17.1.1",
50
+ "@f5xc-salesdemos/pi-agent-core": "17.1.1",
51
+ "@f5xc-salesdemos/pi-ai": "17.1.1",
52
+ "@f5xc-salesdemos/pi-natives": "17.1.1",
53
+ "@f5xc-salesdemos/pi-tui": "17.1.1",
54
+ "@f5xc-salesdemos/pi-utils": "17.1.1",
55
55
  "@sinclair/typebox": "^0.34",
56
56
  "@xterm/headless": "^6.0",
57
57
  "ajv": "^8.18",
@@ -831,6 +831,29 @@ export class ModelRegistry {
831
831
  this.#backgroundRefresh = refreshPromise;
832
832
  }
833
833
 
834
+ /**
835
+ * Await the in-flight background refresh if one is running.
836
+ * Returns immediately if no background refresh is in progress.
837
+ */
838
+ async awaitBackgroundRefresh(): Promise<void> {
839
+ if (this.#backgroundRefresh) {
840
+ await this.#backgroundRefresh;
841
+ }
842
+ }
843
+
844
+ /**
845
+ * Check if any non-optional discoverable provider has no cached models yet.
846
+ * Returns true on first run when the model cache is empty.
847
+ */
848
+ hasUncachedDiscoverableProviders(): boolean {
849
+ for (const [, state] of this.#providerDiscoveryStates) {
850
+ if (state.status === "idle" && !state.optional) {
851
+ return true;
852
+ }
853
+ }
854
+ return false;
855
+ }
856
+
834
857
  async refreshProvider(providerId: string, strategy: ModelRefreshStrategy = "online"): Promise<void> {
835
858
  this.#reloadStaticModels();
836
859
  for (const selector of this.#suppressedSelectors.keys()) {
package/src/main.ts CHANGED
@@ -624,6 +624,19 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
624
624
 
625
625
  const cwd = getProjectDir();
626
626
  await logger.time("settings:init", Settings.init, { cwd });
627
+
628
+ // F5 XC profile loading — optional, never blocks startup.
629
+ // NOTE: This runs in the CLI path only. SDK consumers using createAgentSession()
630
+ // directly must call ProfileService.init(configDir).loadActive() themselves.
631
+ try {
632
+ const { ProfileService } = await import("./services/f5xc-profile");
633
+ const { getF5XCConfigDir } = await import("@f5xc-salesdemos/pi-utils");
634
+ const profileService = ProfileService.init(getF5XCConfigDir());
635
+ await profileService.loadActive();
636
+ } catch {
637
+ // F5 XC auth is optional — silently continue if anything fails
638
+ }
639
+
627
640
  if (parsedArgs.mode === "rpc") {
628
641
  applyRpcDefaultSettingOverrides();
629
642
  }
package/src/sdk.ts CHANGED
@@ -23,11 +23,11 @@ import {
23
23
  prompt,
24
24
  Snowflake,
25
25
  } from "@f5xc-salesdemos/pi-utils";
26
- import chalk from "chalk";
27
26
  import { AsyncJobManager, isBackgroundJobSupportEnabled } from "./async";
28
27
  import { createAutoresearchExtension } from "./autoresearch";
29
28
  import { loadCapability } from "./capability";
30
29
  import { type Rule, ruleCapability } from "./capability/rule";
30
+ import { hasLiteLLMEnv } from "./config/auto-config";
31
31
  import { ModelRegistry } from "./config/model-registry";
32
32
  import { formatModelString, parseModelPattern, parseModelString, resolveModelRoleValue } from "./config/model-resolver";
33
33
  import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./config/prompt-templates";
@@ -780,6 +780,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
780
780
  const modelMatchPreferences = {
781
781
  usageOrder: settings.getStorage()?.getModelUsageOrder(),
782
782
  };
783
+ // When LiteLLM is configured and no model cache exists yet (first run),
784
+ // await the background refresh so model discovery from the proxy completes
785
+ // before we select a default model. Bounded by the 3s probe timeout.
786
+ if (!options.modelRegistry && hasLiteLLMEnv() && modelRegistry.hasUncachedDiscoverableProviders()) {
787
+ await logger.time("awaitLiteLLMDiscovery", () => modelRegistry.awaitBackgroundRefresh());
788
+ }
789
+
783
790
  const defaultRoleSpec = logger.time("resolveDefaultModelRole", () =>
784
791
  resolveModelRoleValue(settings.getModelRole("default"), modelRegistry.getAvailable(), {
785
792
  settings,
@@ -1069,8 +1076,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1069
1076
  if (enableMCP) {
1070
1077
  const mcpResult = await logger.time("discoverAndLoadMCPTools", discoverAndLoadMCPTools, cwd, {
1071
1078
  onConnecting: serverNames => {
1072
- if (options.hasUI && serverNames.length > 0) {
1073
- process.stderr.write(`${chalk.gray(`Connecting to MCP servers: ${serverNames.join(", ")}…`)}\n`);
1079
+ if (serverNames.length > 0) {
1080
+ logger.debug("Connecting to MCP servers", { servers: serverNames });
1074
1081
  }
1075
1082
  },
1076
1083
  enableProjectConfig: settings.get("mcp.enableProjectConfig") ?? true,
package/src/tools/vim.ts CHANGED
@@ -644,7 +644,19 @@ export class VimTool implements AgentTool<typeof vimSchema, VimToolDetails> {
644
644
 
645
645
  await executeVimSteps(engine, steps, {
646
646
  pauseLastStep: params.pause === true,
647
- onKbdStep: emitUpdate ? () => emitUpdate() : undefined,
647
+ onKbdStep: emitUpdate
648
+ ? () => {
649
+ // Force update in prompt modes (command/search) so every keystroke
650
+ // is reported to onUpdate — users need to see each character of
651
+ // their ex-command input, and throttling can cause intermediate
652
+ // states to be skipped under heavy event-loop load (CI).
653
+ const forcePrompt =
654
+ engine.inputMode === "command" ||
655
+ engine.inputMode === "search-forward" ||
656
+ engine.inputMode === "search-backward";
657
+ return emitUpdate(forcePrompt);
658
+ }
659
+ : undefined,
648
660
  onInsertStep: emitUpdate ? () => emitUpdate(true) : undefined,
649
661
  });
650
662
 
@@ -71,8 +71,12 @@ export async function resolveProviderChain(
71
71
  const providers: SearchProvider[] = [];
72
72
 
73
73
  if (preferredProvider !== "auto") {
74
- if (await getSearchProvider(preferredProvider).isAvailable()) {
75
- providers.push(getSearchProvider(preferredProvider));
74
+ try {
75
+ if (await getSearchProvider(preferredProvider).isAvailable()) {
76
+ providers.push(getSearchProvider(preferredProvider));
77
+ }
78
+ } catch {
79
+ // Preferred provider check failed; continue with fallback chain
76
80
  }
77
81
  }
78
82
 
@@ -80,8 +84,12 @@ export async function resolveProviderChain(
80
84
  if (id === preferredProvider) continue;
81
85
 
82
86
  const provider = getSearchProvider(id);
83
- if (await provider.isAvailable()) {
84
- providers.push(provider);
87
+ try {
88
+ if (await provider.isAvailable()) {
89
+ providers.push(provider);
90
+ }
91
+ } catch {
92
+ // Provider availability check failed; skip and continue
85
93
  }
86
94
  }
87
95