osborn 0.9.190 → 0.9.192

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.
@@ -62,6 +62,11 @@ function stripMarkdownForTTS(text) {
62
62
  .replace(/ +/g, ' ')
63
63
  .trim();
64
64
  }
65
+ function getSubagentsDir(workingDir) {
66
+ const dir = join(workingDir, 'subagents');
67
+ mkdirSync(dir, { recursive: true });
68
+ return dir;
69
+ }
65
70
  /**
66
71
  * Load skill files from agent/.claude/skills/{name}/SKILL.md
67
72
  * Injects into system prompt so Claude sees them as available capabilities.
@@ -1332,7 +1337,7 @@ export class ClaudeLLM extends llm.LLM {
1332
1337
  // agents roster prevents any SubagentStop(agent_type==='writer') from
1333
1338
  // firing inside this one-shot query and re-arming the backstop loop.
1334
1339
  const reviewerOptions = {
1335
- cwd: this.#opts.workingDirectory,
1340
+ cwd: getSubagentsDir(this.#opts.workingDirectory),
1336
1341
  permissionMode: 'default',
1337
1342
  systemPrompt: NAMED_AGENTS.reviewer.prompt,
1338
1343
  allowedTools: ['Read', 'Glob', 'Grep', 'Bash', 'Write', 'Edit'],
@@ -1441,7 +1446,7 @@ export class ClaudeLLM extends llm.LLM {
1441
1446
  gitDiff,
1442
1447
  ].join('\n');
1443
1448
  const testerOptions = {
1444
- cwd: this.#opts.workingDirectory,
1449
+ cwd: getSubagentsDir(this.#opts.workingDirectory),
1445
1450
  permissionMode: 'default',
1446
1451
  systemPrompt: NAMED_AGENTS.tester.prompt,
1447
1452
  allowedTools: ['Read', 'Glob', 'Grep', 'Bash', 'Write', 'Edit'],
@@ -1528,7 +1533,7 @@ export class ClaudeLLM extends llm.LLM {
1528
1533
  // an agents roster would allow delegation back to the writer, which would
1529
1534
  // fire SubagentStop(agent_type==='writer') and re-arm the backstop.
1530
1535
  const gateOptions = {
1531
- cwd: this.#opts.workingDirectory,
1536
+ cwd: getSubagentsDir(this.#opts.workingDirectory),
1532
1537
  permissionMode: 'default',
1533
1538
  systemPrompt: NAMED_AGENTS.reasoner.prompt,
1534
1539
  hooks: {
package/dist/config.d.ts CHANGED
@@ -2,8 +2,8 @@ import type { McpServerConfig } from './claude-handler.js';
2
2
  export type VoiceMode = 'pipeline';
3
3
  export type EditMode = 'read-only' | 'edit';
4
4
  export type AgentMode = 'plan' | 'execute' | 'research';
5
- export type STTProvider = 'deepgram' | 'groq-whisper' | 'openai-whisper';
6
- export type TTSProvider = 'openai' | 'deepgram';
5
+ export type STTProvider = 'soniox' | 'deepgram' | 'groq-whisper' | 'openai-whisper';
6
+ export type TTSProvider = 'soniox' | 'openai' | 'deepgram';
7
7
  export interface DirectConfig {
8
8
  stt?: {
9
9
  provider?: STTProvider;
@@ -146,7 +146,7 @@ export declare function invalidateSessionListCache(): void;
146
146
  *
147
147
  * @param limit - Max sessions to return (default 100, sorted by recency)
148
148
  */
149
- export declare function listAllClaudeSessions(limit?: number): Promise<ClaudeSessionEntry[]>;
149
+ export declare function listAllClaudeSessions(limit?: number, includeSubagents?: boolean): Promise<ClaudeSessionEntry[]>;
150
150
  /**
151
151
  * Session summary for context briefing when switching sessions
152
152
  */
package/dist/config.js CHANGED
@@ -47,13 +47,29 @@ const DEFAULT_CONFIG = {
47
47
  voiceMode: 'pipeline',
48
48
  direct: {
49
49
  stt: {
50
- provider: 'deepgram',
51
- model: 'nova-3',
50
+ // Soniox stt-rt-v4 — semantic endpointing (ML, not silence), word timestamps,
51
+ // custom vocabulary via context.terms. ~60% cheaper than nova-3 ($0.0017 vs $0.0043/min).
52
+ // Needs SONIOX_API_KEY.
53
+ provider: 'soniox',
54
+ model: 'stt-rt-v4',
55
+ // Previous: Deepgram nova-3, silence-based endpointing (550ms configured in voice-io.ts).
56
+ // Switch back: provider: 'deepgram', model: 'nova-3'
52
57
  },
53
58
  tts: {
54
- provider: 'openai',
55
- model: 'tts-1-hd',
56
- voice: 'fable',
59
+ // Soniox tts-rt-v1 — real-time WebSocket streaming, speed control (0.7–1.3x),
60
+ // clean abort on interruption. Estimated ~$4–16/M chars vs OpenAI tts-1-hd $30/M.
61
+ // Pricing: $0.70/hr of generated speech (preview). Needs SONIOX_API_KEY.
62
+ provider: 'soniox',
63
+ model: 'tts-rt-v1',
64
+ voice: 'Maya',
65
+ // Previous: OpenAI tts-1-hd, voice fable — high quality, $30/M chars, ~500ms TTFB.
66
+ // Switch back: provider: 'openai', model: 'tts-1-hd', voice: 'fable'
67
+ // Other options already wired in voice-io.ts:
68
+ // Rime Mist v3: provider: 'rime', voice: 'cove' — 37ms TTFB, $30/M, WebSocket
69
+ // Fish Audio s2-pro: provider: 'fishaudio', voice: '<id>' — $15/M, voice cloning
70
+ // Groq Orpheus: provider: 'groq-orpheus', voice: 'autumn' — fast Groq chips, $22/M
71
+ // Deepgram Aura-2: provider: 'deepgram', model: 'aura-2-asteria-en' — $15/M, ~100ms TTFB
72
+ // OpenAI tts-1: provider: 'openai', model: 'tts-1', voice: 'fable' — $15/M
57
73
  },
58
74
  },
59
75
  mcpServers: {
@@ -555,15 +571,23 @@ export function invalidateSessionListCache() {
555
571
  *
556
572
  * @param limit - Max sessions to return (default 100, sorted by recency)
557
573
  */
558
- export async function listAllClaudeSessions(limit = 1000) {
574
+ export async function listAllClaudeSessions(limit = 1000, includeSubagents = false) {
559
575
  if (_sessionListCache && Date.now() < _sessionListCache.expiresAt) {
560
- return _sessionListCache.data.slice(0, limit);
576
+ const cached = includeSubagents
577
+ ? _sessionListCache.data
578
+ : _sessionListCache.data.filter(s => !s.projectSlug.endsWith('-subagents'));
579
+ return cached.slice(0, limit);
561
580
  }
562
581
  const projectsDir = getClaudeProjectsDir();
563
582
  if (!existsSync(projectsDir))
564
583
  return [];
565
- // 1. Discover all project folders
584
+ // 1. Discover all project folders — skip subagent slugs unless explicitly requested.
585
+ // Subagent sessions live in {workingDir}/subagents, which slugifies to a folder
586
+ // ending in "-subagents". Filtering here keeps the session limit budget for
587
+ // real user sessions and prevents subagent noise from crowding out other projects.
566
588
  const projectFolders = readdirSync(projectsDir).filter(name => {
589
+ if (!includeSubagents && name.endsWith('-subagents'))
590
+ return false;
567
591
  const fullPath = join(projectsDir, name);
568
592
  try {
569
593
  return statSync(fullPath).isDirectory();
package/dist/index.js CHANGED
@@ -3603,9 +3603,19 @@ async function main() {
3603
3603
  // discardAudioIfUninterruptible: true, ttsReadIdleTimeout: 10000,
3604
3604
  // maxUnrecoverableErrors: 3) are what was silently running via caret-resolved
3605
3605
  // 1.4.5 throughout the user's working month. Restoring them.
3606
- const turnDetector = process.env.LIVEKIT_REMOTE_EOT_URL ? new CloudTurnDetector() : undefined;
3606
+ // STT endpointing is the default (nova-3, 25ms silence-based VAD — reliable, fast).
3607
+ // CloudTurnDetector (ML semantic EOT) is used ONLY if LIVEKIT_REMOTE_EOT_URL is set
3608
+ // AND the endpoint passes a startup probe (returns valid JSON probability).
3609
+ // If the probe fails (wrong URL, no auth, non-JSON response), STT is kept.
3610
+ let turnDetection = 'stt';
3611
+ if (process.env.LIVEKIT_REMOTE_EOT_URL) {
3612
+ const detector = new CloudTurnDetector();
3613
+ const eotLive = await detector.probe();
3614
+ if (eotLive)
3615
+ turnDetection = detector;
3616
+ }
3607
3617
  const session = new voice.AgentSession({
3608
- turnDetection: (turnDetector ?? 'stt'),
3618
+ turnDetection,
3609
3619
  preemptiveGeneration: false, // Only fire LLM on final committed transcript, not partial preemptives
3610
3620
  // Commented out — kept for reference. These were added across 0.9.60/0.9.61
3611
3621
  // to try to harden interrupt + TTS handling, but evidence (osbornojure
@@ -5,14 +5,14 @@
5
5
  * because it calls getJobContext(). This shim implements the same _TurnDetector
6
6
  * interface but makes the remote HTTP call directly — no worker framework needed.
7
7
  *
8
+ * Auth: LiveKit Cloud inference requires a signed JWT (same format as room access
9
+ * tokens). We cache the token and refresh it before expiry so we're not signing
10
+ * on every turn.
11
+ *
8
12
  * On LiveKit Cloud (LIVEKIT_REMOTE_EOT_URL set): HTTP call to inference gateway.
9
13
  * Without the URL: Returns 1.0 (always end of turn — STT endpointing handles it).
10
14
  */
11
15
  import type { llm } from '@livekit/agents';
12
- /**
13
- * Implements _TurnDetector interface for LiveKit Cloud remote inference
14
- * without requiring JobContext / worker framework.
15
- */
16
16
  export declare class CloudTurnDetector {
17
17
  #private;
18
18
  readonly model = "lk_end_of_utterance_multilingual";
@@ -20,5 +20,11 @@ export declare class CloudTurnDetector {
20
20
  constructor();
21
21
  unlikelyThreshold(_language?: string): Promise<number | undefined>;
22
22
  supportsLanguage(_language?: string): Promise<boolean>;
23
+ /**
24
+ * Startup probe — send a minimal request to confirm the endpoint returns
25
+ * valid JSON probability. Returns true if the EOT service is live and real.
26
+ * Called once at session init; if false, index.ts falls back to 'stt'.
27
+ */
28
+ probe(): Promise<boolean>;
23
29
  predictEndOfTurn(chatCtx: llm.ChatContext, _timeout?: number): Promise<number>;
24
30
  }
@@ -5,19 +5,24 @@
5
5
  * because it calls getJobContext(). This shim implements the same _TurnDetector
6
6
  * interface but makes the remote HTTP call directly — no worker framework needed.
7
7
  *
8
+ * Auth: LiveKit Cloud inference requires a signed JWT (same format as room access
9
+ * tokens). We cache the token and refresh it before expiry so we're not signing
10
+ * on every turn.
11
+ *
8
12
  * On LiveKit Cloud (LIVEKIT_REMOTE_EOT_URL set): HTTP call to inference gateway.
9
13
  * Without the URL: Returns 1.0 (always end of turn — STT endpointing handles it).
10
14
  */
11
15
  import { log } from '@livekit/agents';
16
+ import { AccessToken } from 'livekit-server-sdk';
12
17
  const REMOTE_INFERENCE_TIMEOUT = 2000;
13
18
  const MAX_HISTORY_TURNS = 15;
14
- /**
15
- * Implements _TurnDetector interface for LiveKit Cloud remote inference
16
- * without requiring JobContext / worker framework.
17
- */
19
+ const TOKEN_TTL_SECONDS = 600; // 10-minute JWT
20
+ const TOKEN_REFRESH_BUFFER = 60; // refresh 60s before expiry
18
21
  export class CloudTurnDetector {
19
22
  #remoteUrl;
20
23
  #logger = log();
24
+ #cachedToken;
25
+ #tokenExpiresAt = 0;
21
26
  model = 'lk_end_of_utterance_multilingual';
22
27
  provider = 'livekit';
23
28
  constructor() {
@@ -30,18 +35,78 @@ export class CloudTurnDetector {
30
35
  console.log(`🧠 Turn detector: LiveKit Cloud remote inference (${this.#remoteUrl})`);
31
36
  }
32
37
  else {
33
- console.log('🧠 Turn detector: No LIVEKIT_REMOTE_EOT_URL — STT endpointing fallback');
38
+ console.log('🧠 Turn detector: No LIVEKIT_REMOTE_EOT_URL — STT endpointing only');
39
+ }
40
+ }
41
+ async #getAuthToken() {
42
+ const apiKey = process.env.LIVEKIT_API_KEY;
43
+ const apiSecret = process.env.LIVEKIT_API_SECRET;
44
+ if (!apiKey || !apiSecret)
45
+ return undefined;
46
+ const nowSec = Math.floor(Date.now() / 1000);
47
+ if (this.#cachedToken && nowSec < this.#tokenExpiresAt - TOKEN_REFRESH_BUFFER) {
48
+ return this.#cachedToken;
34
49
  }
50
+ // Sign a fresh token — identity is the agent, no room grants needed for inference
51
+ const at = new AccessToken(apiKey, apiSecret, {
52
+ identity: 'osborn-eot-agent',
53
+ ttl: `${TOKEN_TTL_SECONDS}s`,
54
+ });
55
+ this.#cachedToken = await at.toJwt();
56
+ this.#tokenExpiresAt = nowSec + TOKEN_TTL_SECONDS;
57
+ return this.#cachedToken;
35
58
  }
36
59
  async unlikelyThreshold(_language) {
37
- return undefined; // Let the framework use defaults
60
+ return undefined;
38
61
  }
39
62
  async supportsLanguage(_language) {
40
- return true; // Multilingual model supports all languages
63
+ return true;
64
+ }
65
+ /**
66
+ * Startup probe — send a minimal request to confirm the endpoint returns
67
+ * valid JSON probability. Returns true if the EOT service is live and real.
68
+ * Called once at session init; if false, index.ts falls back to 'stt'.
69
+ */
70
+ async probe() {
71
+ if (!this.#remoteUrl)
72
+ return false;
73
+ try {
74
+ const token = await this.#getAuthToken();
75
+ const headers = { 'Content-Type': 'application/json' };
76
+ if (token)
77
+ headers['Authorization'] = `Bearer ${token}`;
78
+ const resp = await fetch(`${this.#remoteUrl}/eot/multi`, {
79
+ method: 'POST',
80
+ body: JSON.stringify({
81
+ messages: [{ role: 'user', content: 'test' }],
82
+ jobId: 'osborn-probe',
83
+ workerId: 'osborn-direct',
84
+ }),
85
+ headers,
86
+ signal: AbortSignal.timeout(3000),
87
+ });
88
+ if (!resp.ok)
89
+ return false;
90
+ const text = await resp.text();
91
+ try {
92
+ const data = JSON.parse(text);
93
+ const ok = typeof data.probability === 'number';
94
+ console.log(`🧠 EOT probe: ${ok ? '✅ live' : '❌ non-JSON ("' + text.slice(0, 30) + '")'} — ${ok ? 'using CloudTurnDetector' : 'falling back to STT'}`);
95
+ return ok;
96
+ }
97
+ catch {
98
+ console.log(`🧠 EOT probe: ❌ non-JSON response ("${text.slice(0, 40)}") — falling back to STT endpointing`);
99
+ return false;
100
+ }
101
+ }
102
+ catch (err) {
103
+ console.log(`🧠 EOT probe: ❌ unreachable — ${err instanceof Error ? err.message : err} — falling back to STT`);
104
+ return false;
105
+ }
41
106
  }
42
107
  async predictEndOfTurn(chatCtx, _timeout) {
43
108
  if (!this.#remoteUrl) {
44
- return 1.0; // No remote URL = always end of turn (STT handles it)
109
+ return 1.0;
45
110
  }
46
111
  try {
47
112
  const messages = chatCtx
@@ -57,31 +122,41 @@ export class CloudTurnDetector {
57
122
  excludeAudio: true,
58
123
  excludeTimestamp: true,
59
124
  }),
60
- // Dummy IDs — LiveKit Cloud uses these for routing/logging, not auth
61
125
  jobId: `osborn-${Date.now()}`,
62
126
  workerId: 'osborn-direct',
63
127
  };
64
128
  const agentId = process.env.LIVEKIT_AGENT_ID;
65
- if (agentId) {
129
+ if (agentId)
66
130
  request.agentId = agentId;
67
- }
131
+ const token = await this.#getAuthToken();
132
+ const headers = { 'Content-Type': 'application/json' };
133
+ if (token)
134
+ headers['Authorization'] = `Bearer ${token}`;
68
135
  const resp = await fetch(`${this.#remoteUrl}/eot/multi`, {
69
136
  method: 'POST',
70
137
  body: JSON.stringify(request),
71
- headers: { 'Content-Type': 'application/json' },
138
+ headers,
72
139
  signal: AbortSignal.timeout(REMOTE_INFERENCE_TIMEOUT),
73
140
  });
74
141
  if (!resp.ok) {
75
- return 1.0; // Failed — default to end of turn
142
+ this.#logger.warn(`EOT inference returned ${resp.status} — falling back to STT`);
143
+ return 1.0;
76
144
  }
77
- const data = (await resp.json());
78
- if (typeof data.probability === 'number' && data.probability >= 0) {
79
- return data.probability;
145
+ const text = await resp.text();
146
+ try {
147
+ const data = JSON.parse(text);
148
+ if (typeof data.probability === 'number' && data.probability >= 0) {
149
+ return data.probability;
150
+ }
151
+ }
152
+ catch {
153
+ // Non-JSON response (e.g. "OK") — log once then fall through
154
+ this.#logger.warn(`EOT inference returned non-JSON: "${text.slice(0, 40)}" — auth may be wrong`);
80
155
  }
81
156
  return 1.0;
82
157
  }
83
158
  catch {
84
- return 1.0; // Timeout/error — default to end of turn
159
+ return 1.0;
85
160
  }
86
161
  }
87
162
  }
@@ -5,8 +5,9 @@
5
5
  import * as deepgram from '@livekit/agents-plugin-deepgram';
6
6
  import * as openai from '@livekit/agents-plugin-openai';
7
7
  import * as silero from '@livekit/agents-plugin-silero';
8
+ import * as soniox from '@livekit/agents-plugin-soniox';
8
9
  export interface STTConfig {
9
- provider: 'deepgram' | 'deepgram-flux' | 'groq-whisper' | 'openai-whisper';
10
+ provider: 'soniox' | 'deepgram' | 'deepgram-flux' | 'groq-whisper' | 'openai-whisper';
10
11
  model?: string;
11
12
  language?: string;
12
13
  /** Deepgram Flux: end-of-turn confidence threshold (0.0-1.0, default 0.7) */
@@ -15,7 +16,7 @@ export interface STTConfig {
15
16
  eotTimeoutMs?: number;
16
17
  }
17
18
  export interface TTSConfig {
18
- provider: 'openai' | 'deepgram' | 'groq-orpheus' | 'fishaudio' | 'rime';
19
+ provider: 'soniox' | 'openai' | 'deepgram' | 'groq-orpheus' | 'fishaudio' | 'rime';
19
20
  voice?: string;
20
21
  model?: string;
21
22
  }
@@ -27,7 +28,7 @@ export interface VoiceIOConfig {
27
28
  * Create STT (Speech-to-Text) instance based on config
28
29
  * Note: Gemini STT is not available in Node.js, using Deepgram as default
29
30
  */
30
- export declare function createSTT(config: STTConfig): deepgram.STT | deepgram.STTv2 | openai.STT;
31
+ export declare function createSTT(config: STTConfig): soniox.STT | deepgram.STT | deepgram.STTv2 | openai.STT;
31
32
  /**
32
33
  * Create TTS (Text-to-Speech) instance based on config
33
34
  */
package/dist/voice-io.js CHANGED
@@ -7,13 +7,31 @@ import * as fishaudio from '@livekit/agents-plugin-fishaudio';
7
7
  import * as openai from '@livekit/agents-plugin-openai';
8
8
  import * as rime from '@livekit/agents-plugin-rime';
9
9
  import * as silero from '@livekit/agents-plugin-silero';
10
+ import * as soniox from '@livekit/agents-plugin-soniox';
10
11
  /**
11
12
  * Create STT (Speech-to-Text) instance based on config
12
13
  * Note: Gemini STT is not available in Node.js, using Deepgram as default
13
14
  */
14
15
  export function createSTT(config) {
15
16
  switch (config.provider) {
17
+ case 'soniox':
18
+ // Soniox stt-rt-v4 — semantic endpointing: ML model holds on incomplete thoughts,
19
+ // commits on natural sentence ends. maxEndpointDelayMs 500–3000ms (minimum = fastest).
20
+ // endpointLatencyAdjustmentLevel 0–3: higher = more aggressive latency reduction
21
+ // while keeping semantic smarts. context.terms biases recognition toward code vocab.
22
+ return new soniox.STT({
23
+ model: (config.model || 'stt-rt-v4'),
24
+ languageHints: config.language ? [config.language] : ['en'],
25
+ maxEndpointDelayMs: 1200, // give model room to decide on mid-thought pauses
26
+ endpointLatencyAdjustmentLevel: 2, // aggressive but not max — good for voice assistant
27
+ context: {
28
+ terms: ['Claude', 'TypeScript', 'LiveKit', 'Deepgram', 'npm', 'Railway', 'Fly.io'],
29
+ },
30
+ });
16
31
  case 'deepgram':
32
+ // Previous default. Silence-based endpointing (550ms configured = wait for 550ms quiet).
33
+ // Fast and reliable, but commits on any pause — doesn't understand mid-thought hesitations.
34
+ // Switch back: provider: 'deepgram', model: 'nova-3'
17
35
  return new deepgram.STT({
18
36
  model: (config.model || 'nova-3'),
19
37
  language: config.language || 'en',
@@ -49,13 +67,30 @@ export function createSTT(config) {
49
67
  export function createTTS(config) {
50
68
  let tts;
51
69
  switch (config.provider) {
70
+ case 'soniox':
71
+ // Soniox tts-rt-v1 — real-time WebSocket streaming, clean abort on interruption.
72
+ // Estimated ~$4–16/M chars ($0.70/hr of generated speech, preview pricing).
73
+ // speed: 0.7–1.3x. voices: Maya (female), others at soniox.com/docs/tts/voices.
74
+ // Previous TTS: OpenAI tts-1-hd (fable) — $30/M chars, ~500ms TTFB, HTTP chunked.
75
+ // Switch back: provider: 'openai', model: 'tts-1-hd', voice: 'fable'
76
+ tts = new soniox.TTS({
77
+ model: (config.model || 'tts-rt-v1'),
78
+ voice: config.voice || 'Maya',
79
+ speed: 1.0,
80
+ });
81
+ break;
52
82
  case 'openai':
83
+ // tts-1-hd: $30/M chars, ~500ms TTFB, 6 voices: alloy echo fable onyx nova shimmer.
84
+ // tts-1 (cheaper): $15/M chars, slightly lower quality, same voices.
53
85
  tts = new openai.TTS({
54
86
  voice: config.voice || 'alloy',
55
87
  model: config.model || 'tts-1',
56
88
  });
57
89
  break;
58
90
  case 'deepgram':
91
+ // Aura-2 voices: aura-2-asteria-en, aura-2-luna-en, aura-2-stella-en, aura-2-hera-en
92
+ // aura-2-orion-en, aura-2-arcas-en, aura-2-perseus-en, aura-2-angus-en, aura-2-orpheus-en
93
+ // ~$15/M chars, ~100ms TTFB.
59
94
  tts = new deepgram.TTS({
60
95
  model: (config.model || 'aura-2-asteria-en'),
61
96
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.190",
3
+ "version": "0.9.192",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,6 +39,7 @@
39
39
  "@livekit/agents-plugin-openai": "1.4.6",
40
40
  "@livekit/agents-plugin-rime": "1.4.6",
41
41
  "@livekit/agents-plugin-silero": "1.4.6",
42
+ "@livekit/agents-plugin-soniox": "^1.8.1",
42
43
  "@livekit/rtc-node": "0.13.29",
43
44
  "@modelcontextprotocol/sdk": "^1.29.0",
44
45
  "@smithery/api": "^0.48.0",