@tyvm/knowhow 0.0.99 → 0.0.101

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 (47) hide show
  1. package/jest.manual.config.js +19 -0
  2. package/package.json +3 -1
  3. package/scripts/build-for-node.sh +162 -0
  4. package/src/agents/base/base.ts +32 -0
  5. package/src/cli.ts +23 -2
  6. package/src/clients/contextLimits.ts +0 -2
  7. package/src/clients/index.ts +19 -6
  8. package/src/clients/pricing/anthropic.ts +0 -6
  9. package/src/clients/pricing/google.ts +0 -4
  10. package/src/clients/pricing/xai.ts +11 -0
  11. package/src/login.ts +1 -4
  12. package/src/services/Mcp.ts +17 -3
  13. package/src/types.ts +17 -17
  14. package/tests/manual/clients/completions.json +454 -0
  15. package/tests/manual/clients/completions.test.ts +166 -0
  16. package/ts_build/package.json +3 -1
  17. package/ts_build/src/agents/base/base.js +15 -0
  18. package/ts_build/src/agents/base/base.js.map +1 -1
  19. package/ts_build/src/cli.js +12 -2
  20. package/ts_build/src/cli.js.map +1 -1
  21. package/ts_build/src/clients/anthropic.d.ts +0 -6
  22. package/ts_build/src/clients/contextLimits.js +0 -2
  23. package/ts_build/src/clients/contextLimits.js.map +1 -1
  24. package/ts_build/src/clients/index.js +16 -6
  25. package/ts_build/src/clients/index.js.map +1 -1
  26. package/ts_build/src/clients/pricing/anthropic.d.ts +0 -6
  27. package/ts_build/src/clients/pricing/anthropic.js +0 -6
  28. package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
  29. package/ts_build/src/clients/pricing/google.js +0 -4
  30. package/ts_build/src/clients/pricing/google.js.map +1 -1
  31. package/ts_build/src/clients/pricing/xai.d.ts +10 -0
  32. package/ts_build/src/clients/pricing/xai.js +10 -0
  33. package/ts_build/src/clients/pricing/xai.js.map +1 -1
  34. package/ts_build/src/clients/xai.d.ts +13 -4
  35. package/ts_build/src/login.js +1 -4
  36. package/ts_build/src/login.js.map +1 -1
  37. package/ts_build/src/services/Mcp.js +11 -3
  38. package/ts_build/src/services/Mcp.js.map +1 -1
  39. package/ts_build/src/types.d.ts +2 -2
  40. package/ts_build/src/types.js +12 -13
  41. package/ts_build/src/types.js.map +1 -1
  42. package/ts_build/tests/manual/clients/completions.test.d.ts +1 -0
  43. package/ts_build/tests/manual/clients/completions.test.js +135 -0
  44. package/ts_build/tests/manual/clients/completions.test.js.map +1 -0
  45. package/test-ai-completion.ts +0 -39
  46. package/test-mcp-args.ts +0 -71
  47. package/test-tools-service.ts +0 -45
@@ -0,0 +1,19 @@
1
+ module.exports = {
2
+ extensionsToTreatAsEsm: ['.ts'],
3
+ moduleNameMapper: {
4
+ '^(\\.{1,2}/.*)\\.js$': '$1',
5
+ },
6
+ transform: {
7
+ '^.+\\.ts?$': [
8
+ 'ts-jest',
9
+ {
10
+ useESM: true,
11
+ },
12
+ ],
13
+ },
14
+ testEnvironment: 'node',
15
+ testRegex: '/tests/manual/.*\.(test|spec)\.(ts|tsx|js)$',
16
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
17
+ modulePathIgnorePatterns: ["ts_build", "benchmarks"],
18
+ testPathIgnorePatterns: [],
19
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.99",
3
+ "version": "0.0.101",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -9,8 +9,10 @@
9
9
  "scripts": {
10
10
  "test": "jest --testTimeout 300000",
11
11
  "test:debug": "node --inspect-brk ../../node_modules/jest/bin/jest.js --detectOpenHandles --forceExit --testTimeout 300000",
12
+ "build": "npm run compile",
12
13
  "compile": "npm run clean && tsc",
13
14
  "clean": "rm -rf ts_build",
15
+ "node:build": "bash scripts/build-for-node.sh",
14
16
  "start": "npm run compile && node --no-node-snapshot ts_build/src/server/index.js",
15
17
  "dataset:diffs:generate": "ts-node src/dataset/diffs/generate.ts",
16
18
  "dataset:diffs:jsonl": "ts-node src/dataset/diffs/jsonl.ts",
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env bash
2
+ # Usage: bash scripts/build-for-node.sh [node-version]
3
+ # Example: bash scripts/build-for-node.sh 24 # any Node 24.x
4
+ # Example: bash scripts/build-for-node.sh 20.19.0 # exact version
5
+ # Example: npm run node:build 24
6
+ #
7
+ # This script:
8
+ # 1. Compiles TypeScript with Node 20 (required for workspace deps)
9
+ # 2. Creates /tmp/knowhow-node-<major> with the compiled output
10
+ # 3. Installs the correct isolated-vm version for the target node in that dir
11
+ # 4. Symlinks the package globally for ALL installed nvm versions matching the target
12
+ #
13
+ # This approach avoids polluting the workspace node_modules with a different
14
+ # isolated-vm ABI, so Node 20 and Node 24 builds can coexist.
15
+
16
+ set -e
17
+
18
+ TARGET_VERSION="${1:-}"
19
+
20
+ if [ -z "$TARGET_VERSION" ]; then
21
+ echo "Usage: $0 <node-version>"
22
+ echo "Example: $0 24"
23
+ echo "Example: $0 20.19.0"
24
+ exit 1
25
+ fi
26
+
27
+ # Extract the major version number for staging dir naming and glob matching
28
+ TARGET_MAJOR="$(echo "$TARGET_VERSION" | cut -d. -f1)"
29
+
30
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31
+ PACKAGE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
32
+
33
+ echo "📦 Package dir: $PACKAGE_DIR"
34
+
35
+ # --- Find Node 20 for compiling TypeScript ---
36
+ NODE20_BIN=""
37
+ for dir in "$HOME/.nvm/versions/node"/v20.*/bin; do
38
+ if [ -f "$dir/node" ]; then
39
+ NODE20_BIN="$dir/node"
40
+ break
41
+ fi
42
+ done
43
+
44
+ if [ -z "$NODE20_BIN" ]; then
45
+ echo "⚠️ Node 20 not found via nvm, falling back to current node for TS compile"
46
+ NODE20_BIN="$(which node)"
47
+ fi
48
+
49
+ NODE20_NPM="$(dirname "$NODE20_BIN")/npm"
50
+ echo "🔨 Compiling TypeScript with: $NODE20_BIN ($(${NODE20_BIN} --version))"
51
+
52
+ # --- Compile TypeScript ---
53
+ cd "$PACKAGE_DIR"
54
+ "$NODE20_NPM" run compile
55
+ echo "✅ TypeScript compiled"
56
+
57
+ # --- Find target Node binaries ---
58
+ # If exact version given (e.g. 20.19.0), match exactly. Otherwise match all patch versions.
59
+ TARGET_NODE_BINS=()
60
+
61
+ if echo "$TARGET_VERSION" | grep -qE '^\d+\.\d+\.\d+$'; then
62
+ # Exact version like 20.19.0
63
+ exact_dir="$HOME/.nvm/versions/node/v${TARGET_VERSION}/bin"
64
+ if [ -f "$exact_dir/node" ]; then
65
+ TARGET_NODE_BINS+=("$exact_dir/node")
66
+ fi
67
+ else
68
+ # Major only — collect all patch versions
69
+ for dir in "$HOME/.nvm/versions/node"/v${TARGET_MAJOR}.*/bin; do
70
+ if [ -f "$dir/node" ]; then
71
+ TARGET_NODE_BINS+=("$dir/node")
72
+ fi
73
+ done
74
+ fi
75
+
76
+ if [ ${#TARGET_NODE_BINS[@]} -eq 0 ]; then
77
+ echo "❌ Node $TARGET_VERSION not found in ~/.nvm/versions/node/"
78
+ echo " Run: nvm install $TARGET_VERSION"
79
+ exit 1
80
+ fi
81
+
82
+ # Use the last (latest patch) for building
83
+ TARGET_NODE_BIN="${TARGET_NODE_BINS[${#TARGET_NODE_BINS[@]}-1]}"
84
+ TARGET_NODE_NPM="$(dirname "$TARGET_NODE_BIN")/npm"
85
+ TARGET_NODE_DIR="$(dirname "$TARGET_NODE_BIN")"
86
+ TARGET_NODE_ACTUAL_VERSION="$("$TARGET_NODE_BIN" --version)"
87
+
88
+ echo "🎯 Found Node $TARGET_VERSION installs: ${TARGET_NODE_BINS[*]}"
89
+ echo "🔨 Building with: $TARGET_NODE_BIN ($TARGET_NODE_ACTUAL_VERSION)"
90
+
91
+ # --- Pick the right isolated-vm version for the target node ---
92
+ # isolated-vm@5.x supports Node <22, isolated-vm@6.x requires Node >=22
93
+ if [ "$TARGET_MAJOR" -ge 22 ]; then
94
+ IVM_VERSION="^6.0.0"
95
+ echo "📌 Using isolated-vm@6.x (Node >= 22)"
96
+ else
97
+ IVM_VERSION="^5.0.4"
98
+ echo "📌 Using isolated-vm@5.x (Node < 22)"
99
+ fi
100
+
101
+ # --- Create staging directory ---
102
+ STAGING_DIR="/tmp/knowhow-node-${TARGET_MAJOR}"
103
+ rm -rf "$STAGING_DIR"
104
+ mkdir -p "$STAGING_DIR"
105
+ echo ""
106
+ echo "📁 Staging dir: $STAGING_DIR"
107
+
108
+ # --- Copy compiled output and package files into staging dir ---
109
+ echo "📋 Copying compiled output to staging dir..."
110
+ cp -r "$PACKAGE_DIR/ts_build" "$STAGING_DIR/ts_build"
111
+ cp -r "$PACKAGE_DIR/bin" "$STAGING_DIR/bin" 2>/dev/null || true
112
+ cp "$PACKAGE_DIR/package.json" "$STAGING_DIR/package.json"
113
+ for item in README.md LICENSE .npmignore; do
114
+ [ -e "$PACKAGE_DIR/$item" ] && cp "$PACKAGE_DIR/$item" "$STAGING_DIR/" || true
115
+ done
116
+
117
+ # --- Patch package.json for target isolated-vm version ---
118
+ echo "📝 Patching package.json for isolated-vm $IVM_VERSION..."
119
+ "$NODE20_BIN" -e "
120
+ const fs = require('fs');
121
+ const pkg = JSON.parse(fs.readFileSync('$STAGING_DIR/package.json', 'utf8'));
122
+ pkg.dependencies['isolated-vm'] = '$IVM_VERSION';
123
+ // Remove workspace protocol deps that won't resolve outside the monorepo
124
+ if (pkg.dependencies) {
125
+ for (const [k, v] of Object.entries(pkg.dependencies)) {
126
+ if (String(v).startsWith('workspace:')) delete pkg.dependencies[k];
127
+ }
128
+ }
129
+ fs.writeFileSync('$STAGING_DIR/package.json', JSON.stringify(pkg, null, 2));
130
+ console.log('✅ package.json patched');
131
+ "
132
+
133
+ # --- Install deps in staging dir using target node ---
134
+ echo ""
135
+ echo "📦 Installing dependencies in staging dir with Node $TARGET_MAJOR..."
136
+ cd "$STAGING_DIR"
137
+ # Prepend target node bin to PATH so npm/node-gyp uses the correct node version
138
+ PATH="$TARGET_NODE_DIR:$PATH" "$TARGET_NODE_NPM" install --no-save 2>&1
139
+ echo "✅ Dependencies installed (isolated-vm compiled for Node $TARGET_MAJOR)"
140
+
141
+ # --- Symlink globally for ALL matching Node version installs ---
142
+ PKG_NAME="$("$NODE20_BIN" -e "console.log(require('$STAGING_DIR/package.json').name)")"
143
+ PKG_BIN_NAME="$("$NODE20_BIN" -e "const b=require('$STAGING_DIR/package.json').bin; console.log(Object.keys(b)[0])")"
144
+ PKG_BIN_FILE="$("$NODE20_BIN" -e "const b=require('$STAGING_DIR/package.json').bin; console.log(Object.values(b)[0])")"
145
+
146
+ echo ""
147
+ echo "🔗 Linking $PKG_NAME globally for all Node $TARGET_MAJOR installs..."
148
+ for node_bin in "${TARGET_NODE_BINS[@]}"; do
149
+ node_prefix="$("$node_bin" -e "const p=require('path');console.log(p.join(p.dirname(process.execPath),'..'))" 2>/dev/null)"
150
+ global_modules="$node_prefix/lib/node_modules"
151
+ global_bin="$node_prefix/bin"
152
+ mkdir -p "$global_modules/@tyvm"
153
+ rm -rf "$global_modules/$PKG_NAME"
154
+ ln -sf "$STAGING_DIR" "$global_modules/$PKG_NAME"
155
+ rm -f "$global_bin/$PKG_BIN_NAME"
156
+ ln -sf "$global_modules/$PKG_NAME/$PKG_BIN_FILE" "$global_bin/$PKG_BIN_NAME"
157
+ echo "✅ $("$node_bin" --version): $global_modules/$PKG_NAME → $STAGING_DIR"
158
+ done
159
+
160
+ echo ""
161
+ echo "🎉 Done! Switch to Node $TARGET_MAJOR and run: knowhow"
162
+ echo " nvm use $TARGET_MAJOR && knowhow"
@@ -619,6 +619,17 @@ export abstract class BaseAgent implements IAgent {
619
619
  tool_choice: "auto",
620
620
  });
621
621
 
622
+ // If the agent was paused while the completion was in-flight, wait here
623
+ // before processing tool calls. This allows the user to send messages
624
+ // (via addPendingUserMessage) and prevents the agent from proceeding to
625
+ // tool calls (e.g. finalAnswer) without seeing those interactions.
626
+ if (this.status === this.eventTypes.pause) {
627
+ this.log(
628
+ "Agent was paused after completion, waiting before processing tool calls"
629
+ );
630
+ await this.unpaused();
631
+ }
632
+
622
633
  if (response?.usd_cost === undefined) {
623
634
  this.log(
624
635
  `Response cost is undefined: ${JSON.stringify(response, null, 2)}`,
@@ -662,6 +673,13 @@ export abstract class BaseAgent implements IAgent {
662
673
  this.updateCurrentThread(messages);
663
674
 
664
675
  for (const toolCall of toolCalls) {
676
+ if(this.status === this.eventTypes.pause) {
677
+ this.log(
678
+ "Agent was paused before tool call, waiting before processing tool calls"
679
+ );
680
+ await this.unpaused();
681
+ }
682
+
665
683
  const toolMessages = await this.processToolMessages(toolCall);
666
684
  // Add the tool responses to the thread
667
685
  messages.push(...(toolMessages as Message[]));
@@ -676,6 +694,18 @@ export abstract class BaseAgent implements IAgent {
676
694
  );
677
695
 
678
696
  if (finalMessage) {
697
+ // If user added pending messages after finalAnswer was called,
698
+ // continue running to respond to that feedback instead of returning
699
+ if (this.pendingUserMessages.length > 0) {
700
+ this.log(
701
+ "finalAnswer called but pending user messages exist, continuing to respond to feedback"
702
+ );
703
+ messages.push(...this.pendingUserMessages);
704
+ this.pendingUserMessages = [];
705
+ this.updateCurrentThread(messages);
706
+ return this.call(userInput, messages);
707
+ }
708
+
679
709
  // Emit task completion event for plugins (like GitPlugin)
680
710
  this.events.emit(this.eventTypes.agentTaskComplete, {
681
711
  taskId:
@@ -685,6 +715,8 @@ export abstract class BaseAgent implements IAgent {
685
715
  result: finalMessage.content || "Done",
686
716
  });
687
717
  const doneMsg = finalMessage.content || "Done";
718
+
719
+
688
720
  this.agentEvents.emit(this.eventTypes.done, doneMsg);
689
721
  this.status = this.eventTypes.done;
690
722
  return doneMsg;
package/src/cli.ts CHANGED
@@ -38,6 +38,20 @@ import { readPromptFile } from "./ai";
38
38
  import { SetupModule } from "./chat/modules/SetupModule";
39
39
  import { CliChatService } from "./chat/CliChatService";
40
40
 
41
+ // Handle unhandled promise rejections gracefully — particularly from MCP SDK
42
+ // which fires errors via event emitters that can bypass Promise.allSettled.
43
+ // Without this, a single failing MCP server (e.g. expired Notion token) will
44
+ // crash the entire CLI with an unhandled rejection.
45
+ process.on("unhandledRejection", (reason: unknown) => {
46
+ const message =
47
+ reason instanceof Error ? reason.message : String(reason);
48
+ // Only warn — don't exit. The MCP connect errors are recoverable;
49
+ // the server will simply be unavailable but others continue working.
50
+ console.warn(
51
+ `⚠ Unhandled MCP/async error (non-fatal): ${message}`
52
+ );
53
+ });
54
+
41
55
  async function setupServices() {
42
56
  const { Agents, Mcp, Clients, Tools: OldTools } = services();
43
57
  const Tools = new LazyToolsService(); // eslint-disable-line no-shadow
@@ -74,7 +88,14 @@ async function setupServices() {
74
88
  Agents.setAgentContext(agentContext);
75
89
 
76
90
  console.log("🔌 Connecting to MCP...");
77
- await Mcp.connectToConfigured(Tools);
91
+ try {
92
+ await Mcp.connectToConfigured(Tools);
93
+ } catch (mcpError) {
94
+ const msg = mcpError instanceof Error ? mcpError.message : String(mcpError);
95
+ console.warn(
96
+ `⚠ Some MCP servers failed to connect (continuing without them): ${msg}`
97
+ );
98
+ }
78
99
  console.log("Connecting to clients...");
79
100
  await Clients.registerConfiguredModels();
80
101
  console.log("✓ Services are set up and ready to go!");
@@ -138,7 +159,7 @@ async function main() {
138
159
  console.log(`Current version: ${version}`);
139
160
 
140
161
  console.log("📦 Installing latest version from npm...");
141
- execSync("npm install -g knowhow@latest", {
162
+ execSync("npm install -g @tyvm/knowhow@latest", {
142
163
  stdio: "inherit",
143
164
  encoding: "utf-8",
144
165
  });
@@ -52,7 +52,6 @@ export const ContextLimits: Record<string, number> = {
52
52
  [Models.anthropic.Haiku4_5]: 200_000,
53
53
  [Models.anthropic.Sonnet3_7]: 200_000,
54
54
  [Models.anthropic.Sonnet3_5]: 200_000,
55
- [Models.anthropic.Haiku3_5]: 200_000,
56
55
  [Models.anthropic.Opus3]: 200_000,
57
56
  [Models.anthropic.Haiku3]: 200_000,
58
57
 
@@ -74,7 +73,6 @@ export const ContextLimits: Record<string, number> = {
74
73
  [Models.google.Gemini_25_Pro_TTS]: 1_000_000,
75
74
  [Models.google.Gemini_20_Flash]: 1_000_000,
76
75
  [Models.google.Gemini_20_Flash_Preview_Image_Generation]: 1_000_000,
77
- [Models.google.Gemini_20_Flash_Lite]: 1_000_000,
78
76
  [Models.google.Gemini_20_Flash_Live]: 1_000_000,
79
77
  [Models.google.Gemini_20_Flash_TTS]: 1_000_000,
80
78
  [Models.google.Gemini_15_Flash]: 1_000_000,
@@ -217,7 +217,12 @@ export class AIClient {
217
217
  for (const entry of providers) {
218
218
  const client = this.resolveClient(entry);
219
219
 
220
- if (!client) continue;
220
+ if (!client) {
221
+ if (entry.provider === "knowhow") {
222
+ console.warn(`⚠️ Knowhow provider is not logged in. Run 'knowhow login' to enable Knowhow models.`);
223
+ }
224
+ continue;
225
+ }
221
226
 
222
227
  const reg = this.providerRegistry[entry.provider];
223
228
 
@@ -493,11 +498,19 @@ export class AIClient {
493
498
  return foundByModel;
494
499
  }
495
500
 
496
- console.log("unable to find", {
497
- provider,
498
- model,
499
- all: this.listAllModels(),
500
- });
501
+ const allModels = this.listAllModels();
502
+ const hasKnowhowModels =
503
+ allModels["knowhow"] && allModels["knowhow"].length > 0;
504
+ const knowhowIsConfigured = Object.keys(allModels).includes("knowhow");
505
+
506
+ console.warn(`⚠️ Unable to find model '${model}' for provider '${provider}'.`);
507
+ console.warn(` Available providers: ${Object.keys(allModels).join(", ") || "(none)"}`);
508
+
509
+ if (!hasKnowhowModels && !knowhowIsConfigured) {
510
+ console.warn(` Tip: Run 'knowhow login' to enable Knowhow models.`);
511
+ } else if (!hasKnowhowModels) {
512
+ console.warn(` Tip: The Knowhow provider returned no models. Try running 'knowhow login' to re-authenticate.`);
513
+ }
501
514
 
502
515
  return { provider, model };
503
516
  }
@@ -65,12 +65,6 @@ export const AnthropicTextPricing = {
65
65
  cache_hit: 0.3,
66
66
  output: 15.0,
67
67
  },
68
- [Models.anthropic.Haiku3_5]: {
69
- input: 0.8,
70
- cache_write: 1.0,
71
- cache_hit: 0.08,
72
- output: 4.0,
73
- },
74
68
  [Models.anthropic.Opus3]: {
75
69
  input: 15.0,
76
70
  cache_write: 18.75,
@@ -174,10 +174,6 @@ export const GeminiPricing: Record<string, GeminiModelPricing> = {
174
174
  output: 0.4,
175
175
  image_generation: 0.039,
176
176
  },
177
- [Models.google.Gemini_20_Flash_Lite]: {
178
- input: 0.075,
179
- output: 0.3,
180
- },
181
177
 
182
178
  // ── Gemini 1.5 (legacy) ───────────────────────────────────────────────────
183
179
  [Models.google.Gemini_15_Flash]: {
@@ -1,6 +1,17 @@
1
1
  import { Models } from "../../types";
2
2
 
3
3
  export const XaiTextPricing = {
4
+
5
+ [Models.xai.Grok_4_20_Reasoning]: {
6
+ input: 2.0,
7
+ cache_hit: 0.20,
8
+ output: 6.0,
9
+ },
10
+ [Models.xai.Grok_4_20_NonReasoning]: {
11
+ input: 2.0,
12
+ cache_hit: 0.20,
13
+ output: 6.0,
14
+ },
4
15
  [Models.xai.Grok4_1_Fast_NonReasoning]: {
5
16
  input: 0.2,
6
17
  cache_hit: 0.05,
package/src/login.ts CHANGED
@@ -49,20 +49,17 @@ export async function login(jwtFlag?: boolean): Promise<void> {
49
49
  );
50
50
 
51
51
  const config = await getConfig();
52
- const proxyUrl = KNOWHOW_API_URL + "/api/proxy";
53
52
 
54
53
  if (!config.modelProviders) {
55
54
  config.modelProviders = [];
56
55
  }
57
56
 
58
57
  const hasProvider = config.modelProviders.find(
59
- (provider) => provider.provider === "knowhow" && provider.url === proxyUrl
58
+ (provider) => provider.provider === "knowhow"
60
59
  );
61
60
  if (!hasProvider) {
62
61
  config.modelProviders.push({
63
62
  provider: "knowhow",
64
- url: proxyUrl,
65
- jwtFile: ".knowhow/.jwt",
66
63
  });
67
64
 
68
65
  await updateConfig(config);
@@ -136,13 +136,24 @@ export class McpService {
136
136
  if (shouldAutoConnect && !this.connected[index]) {
137
137
  console.log(`Connecting to MCP server: ${config.name}`);
138
138
  try {
139
- await client.connect(this.transports[index]);
139
+ // Wrap connect in a race with a timeout to prevent hanging,
140
+ // and use a wrapping Promise to catch errors that may come
141
+ // from event-emitter callbacks (unhandled rejection pattern in MCP SDK)
142
+ await new Promise<void>(async (resolve, reject) => {
143
+ try {
144
+ await client.connect(this.transports[index]);
145
+ resolve();
146
+ } catch (err) {
147
+ reject(err);
148
+ }
149
+ });
140
150
  } catch (error) {
141
151
  console.error(
142
152
  `Failed to connect to MCP server '${config.name}':`,
143
153
  error.message || error
144
154
  );
145
- throw error; // Re-throw to mark as rejected in Promise.allSettled
155
+ // Don't re-throw just log and continue so other servers can still connect
156
+ return;
146
157
  }
147
158
  this.connected[index] = true;
148
159
  } else if (!shouldAutoConnect) {
@@ -155,7 +166,10 @@ export class McpService {
155
166
 
156
167
  // Log summary of auto-connection results
157
168
  const successful = results.filter((r) => r.status === "fulfilled").length;
158
- const failed = results.filter((r) => r.status === "rejected").length;
169
+ // Since we no longer re-throw, count servers that are NOT connected after the attempt
170
+ const failed = this.clients.filter(
171
+ (_, i) => this.config[i]?.autoConnect !== false && !this.connected[i]
172
+ ).length;
159
173
  if (failed > 0) {
160
174
  console.warn(
161
175
  `Auto-connected ${successful}/${this.clients.length} MCP servers (${failed} failed)`
package/src/types.ts CHANGED
@@ -89,9 +89,9 @@ export type Config = {
89
89
  auth?: {
90
90
  required?: boolean;
91
91
  passkey?: {
92
- publicKey?: string; // base64-encoded public key
93
- credentialId?: string; // base64-encoded credential ID
94
- algorithm?: string; // e.g. "ES256"
92
+ publicKey?: string; // base64-encoded public key
93
+ credentialId?: string; // base64-encoded credential ID
94
+ algorithm?: string; // e.g. "ES256"
95
95
  };
96
96
  sessionDurationHours?: number;
97
97
  };
@@ -187,19 +187,20 @@ export const Models = {
187
187
  anthropic: {
188
188
  Opus4_6: "claude-opus-4-6",
189
189
  Sonnet4_6: "claude-sonnet-4-6",
190
- Opus4_5: "claude-opus-4-5-20251101",
191
- Opus4: "claude-opus-4-20250514",
192
- Opus4_1: "claude-opus-4-1-20250805",
193
- Sonnet4_5: "claude-sonnet-4-5-20250929",
194
- Haiku4_5: "claude-haiku-4-5-20251001",
195
- Sonnet4: "claude-sonnet-4-20250514",
196
- Sonnet3_7: "claude-3-7-sonnet-20250219",
197
- Sonnet3_5: "claude-3-5-sonnet-20241022",
198
- Haiku3_5: "claude-3-5-haiku-20241022",
199
- Opus3: "claude-3-opus-20240229",
200
- Haiku3: "claude-3-haiku-20240307",
190
+ Opus4_5: "claude-opus-4-5",
191
+ Opus4: "claude-opus-4",
192
+ Opus4_1: "claude-opus-4-1",
193
+ Sonnet4_5: "claude-sonnet-4-5",
194
+ Haiku4_5: "claude-haiku-4-5",
195
+ Sonnet4: "claude-sonnet-4",
196
+ Sonnet3_7: "claude-3-7-sonnet",
197
+ Sonnet3_5: "claude-3-5-sonnet",
198
+ Opus3: "claude-3-opus",
199
+ Haiku3: "claude-3-haiku",
201
200
  },
202
201
  xai: {
202
+ Grok_4_20_Reasoning: "grok-4.20-0309-reasoning",
203
+ Grok_4_20_NonReasoning: "grok-4.20-0309-non-reasoning",
203
204
  Grok4_1_Fast_Reasoning: "grok-4-1-fast-reasoning",
204
205
  Grok4_1_Fast_NonReasoning: "grok-4-1-fast-non-reasoning",
205
206
  GrokCodeFast: "grok-code-fast-1",
@@ -277,13 +278,13 @@ export const Models = {
277
278
  Gemini_25_Pro_Preview: "gemini-2.5-pro-preview-05-06",
278
279
  Gemini_25_Flash_Image: "gemini-2.5-flash-image",
279
280
  Gemini_25_Flash_Live: "gemini-2.5-flash-live-preview",
280
- Gemini_25_Flash_Native_Audio: "gemini-2.5-flash-native-audio-preview-12-2025",
281
+ Gemini_25_Flash_Native_Audio:
282
+ "gemini-2.5-flash-native-audio-preview-12-2025",
281
283
  Gemini_25_Pro_TTS: "gemini-2.5-pro-preview-tts",
282
284
  // Gemini 2.0 (deprecated)
283
285
  Gemini_20_Flash: "gemini-2.0-flash",
284
286
  Gemini_20_Flash_Preview_Image_Generation:
285
287
  "gemini-2.0-flash-exp-image-generation",
286
- Gemini_20_Flash_Lite: "gemini-2.0-flash-lite",
287
288
  // Gemini 1.5 (legacy)
288
289
  Gemini_15_Flash: "gemini-1.5-flash",
289
290
  Gemini_15_Flash_8B: "gemini-1.5-flash-8b",
@@ -380,7 +381,6 @@ export const GoogleReasoningModels = [
380
381
  Models.google.Gemini_25_Flash_Preview,
381
382
  Models.google.Gemini_25_Pro_Preview,
382
383
  Models.google.Gemini_20_Flash,
383
- Models.google.Gemini_20_Flash_Lite,
384
384
  Models.google.Gemini_15_Flash,
385
385
  Models.google.Gemini_15_Flash_8B,
386
386
  Models.google.Gemini_15_Pro,