@adminide-stack/yantra-mobile 12.0.28-alpha.73 → 12.0.28-alpha.75

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.
@@ -5,7 +5,9 @@ const CDECLI_GATEWAY = {
5
5
  extensionId: "",
6
6
  icon: "terminal",
7
7
  isBuiltin: true,
8
- priority: 90
8
+ priority: 90,
9
+ /** Server persists chat posts via gateway — client must not call `saveMessages`. */
10
+ persistenceMode: "backend"
9
11
  };
10
12
  const BUILTIN_MOBILE_GATEWAYS = {
11
13
  cdecli: CDECLI_GATEWAY
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sources":["../../src/config/constants.ts"],"sourcesContent":["/** Minimal gateway types for mobile — aligned with account/browser. */\n\nexport type GatewayId = string;\n\nexport interface GatewayEntry {\n name: string;\n description: string;\n channelType: string;\n extensionId: string;\n icon: string;\n isBuiltin: boolean;\n priority: number;\n endpoint?: string;\n persistenceMode?: 'backend' | 'frontend';\n}\n\n/** CDeCLI / cdecli-serve (hosted agent). */\nconst CDECLI_GATEWAY: GatewayEntry = {\n name: 'CDeCLI',\n description: 'CDeCLI agent and skills',\n channelType: 'cdecli-serve',\n extensionId: '',\n icon: 'terminal',\n isBuiltin: true,\n priority: 90,\n};\n\n/** Built-in gateways when UiLayout settings are not loaded (e.g. React Native). */\nexport const BUILTIN_MOBILE_GATEWAYS: Record<string, GatewayEntry> = {\n cdecli: CDECLI_GATEWAY,\n};\n\nexport const STORAGE_KEYS = {\n GATEWAY_SELECTION: 'yantra_gateway_selection',\n} as const;\n\n/** Maximum concurrent agent tasks (same cap as browser). */\nexport const MAX_CONCURRENT_TASKS = 3;\n"],"names":[],"mappings":"AAgBA,MAAM,cAA+B,GAAA;AAAA,EACnC,IAAM,EAAA,QAAA;AAAA,EACN,WAAa,EAAA,yBAAA;AAAA,EACb,WAAa,EAAA,cAAA;AAAA,EACb,WAAa,EAAA,EAAA;AAAA,EACb,IAAM,EAAA,UAAA;AAAA,EACN,SAAW,EAAA,IAAA;AAAA,EACX,QAAU,EAAA;AACZ,CAAA;AAGO,MAAM,uBAAwD,GAAA;AAAA,EACnE,MAAQ,EAAA;AACV;AACO,MAAM,YAAe,GAAA;AAAA,EAC1B,iBAAmB,EAAA;AACrB;AAGO,MAAM,oBAAuB,GAAA"}
1
+ {"version":3,"file":"constants.js","sources":["../../src/config/constants.ts"],"sourcesContent":["/** Minimal gateway types for mobile — aligned with account/browser. */\n\nexport type GatewayId = string;\n\nexport interface GatewayEntry {\n name: string;\n description: string;\n channelType: string;\n extensionId: string;\n icon: string;\n isBuiltin: boolean;\n priority: number;\n endpoint?: string;\n persistenceMode?: 'backend' | 'frontend';\n}\n\n/** CDeCLI / cdecli-serve (hosted agent). */\nconst CDECLI_GATEWAY: GatewayEntry = {\n name: 'CDeCLI',\n description: 'CDeCLI agent and skills',\n channelType: 'cdecli-serve',\n extensionId: '',\n icon: 'terminal',\n isBuiltin: true,\n priority: 90,\n /** Server persists chat posts via gateway — client must not call `saveMessages`. */\n persistenceMode: 'backend',\n};\n\n/** Built-in gateways when UiLayout settings are not loaded (e.g. React Native). */\nexport const BUILTIN_MOBILE_GATEWAYS: Record<string, GatewayEntry> = {\n cdecli: CDECLI_GATEWAY,\n};\n\nexport const STORAGE_KEYS = {\n GATEWAY_SELECTION: 'yantra_gateway_selection',\n} as const;\n\n/** Maximum concurrent agent tasks (same cap as browser). */\nexport const MAX_CONCURRENT_TASKS = 3;\n"],"names":[],"mappings":"AAgBA,MAAM,cAA+B,GAAA;AAAA,EACnC,IAAM,EAAA,QAAA;AAAA,EACN,WAAa,EAAA,yBAAA;AAAA,EACb,WAAa,EAAA,cAAA;AAAA,EACb,WAAa,EAAA,EAAA;AAAA,EACb,IAAM,EAAA,UAAA;AAAA,EACN,SAAW,EAAA,IAAA;AAAA,EACX,QAAU,EAAA,EAAA;AAAA;AAAA,EAEV,eAAiB,EAAA;AACnB,CAAA;AAGO,MAAM,uBAAwD,GAAA;AAAA,EACnE,MAAQ,EAAA;AACV;AACO,MAAM,YAAe,GAAA;AAAA,EAC1B,iBAAmB,EAAA;AACrB;AAGO,MAAM,oBAAuB,GAAA"}
@@ -38,8 +38,9 @@ const baseConfig = cleanEnv(env, {
38
38
  default: ""
39
39
  }),
40
40
  /** Seconds to wait for CDeCLI stream completion before timing out (then fallback chat API runs). Min 15, max 600. */
41
+ /** Aligned with web `useCdecliChannel` (120s). Mobile hook uses the same constant inline. */
41
42
  CDECLI_CHAT_RESPONSE_TIMEOUT_SEC: num({
42
- default: 45
43
+ default: 120
43
44
  }),
44
45
  BACKEND_URL: str({
45
46
  default: ""
@@ -1 +1 @@
1
- {"version":3,"file":"env-config.js","sources":["../../src/config/env-config.ts"],"sourcesContent":["import { str, cleanEnv, bool, num } from 'envalid';\n\nconst env = (process as { APP_ENV?: NodeJS.ProcessEnv; env?: NodeJS.ProcessEnv }).APP_ENV || process.env;\n\nconst baseConfig = cleanEnv(env, {\n GRAPHQL_URL: str({ default: 'http://localhost:8080/graphql' }),\n CLIENT_URL: str({ default: 'http://localhost:3000' }),\n APP_NAME: str({ default: 'Yantra' }),\n GROQ_API_KEY: str({ default: '' }),\n CDECLI_AGENT_ENDPOINT: str({ default: 'https://cdecli-agent.cdebase.dev' }),\n CDECLI_AGENT_AUTH_TOKEN: str({ default: '' }),\n /** Seconds to wait for CDeCLI stream completion before timing out (then fallback chat API runs). Min 15, max 600. */\n CDECLI_CHAT_RESPONSE_TIMEOUT_SEC: num({ default: 45 }),\n BACKEND_URL: str({ default: '' }),\n USE_PROXY_STAGING_GATEWAY: bool({ default: false }),\n PROXY_STAGING_GATEWAY_URL: str({ default: '' }),\n});\n\nfunction resolveAgentGatewayGraphqlUrl(): string {\n if (baseConfig.USE_PROXY_STAGING_GATEWAY && baseConfig.PROXY_STAGING_GATEWAY_URL) {\n return baseConfig.PROXY_STAGING_GATEWAY_URL;\n }\n if (baseConfig.GRAPHQL_URL) return baseConfig.GRAPHQL_URL;\n if (baseConfig.BACKEND_URL) return `${baseConfig.BACKEND_URL.replace(/\\/$/, '')}/graphql`;\n return '/graphql';\n}\n\nfunction resolveBaseWsUrl(graphqlUrl: string): string {\n const base = graphqlUrl.replace(/\\/graphql$/, '');\n return base.replace(/^http/, 'ws');\n}\n\nfunction resolveAgentGatewayWsUrl(graphqlUrl: string): string {\n return `${resolveBaseWsUrl(graphqlUrl)}/ws/openclaw-chat`;\n}\n\nconst agentGatewayGraphqlUrl = resolveAgentGatewayGraphqlUrl();\n\nconst clampedCdecliTimeoutSec = Math.min(600, Math.max(15, Math.round(baseConfig.CDECLI_CHAT_RESPONSE_TIMEOUT_SEC)));\n\nexport const config = {\n ...baseConfig,\n CDECLI_CHAT_RESPONSE_TIMEOUT_SEC: clampedCdecliTimeoutSec,\n AGENT_GATEWAY_GRAPHQL_URL: agentGatewayGraphqlUrl,\n AGENT_GATEWAY_WS_URL: resolveAgentGatewayWsUrl(agentGatewayGraphqlUrl),\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AACA,MAAM,GAAA,GAAO,OAGV,CAAA,OAAA,IAAW,OAAQ,CAAA,GAAA;AACtB,MAAM,UAAA,GAAa,SAAS,GAAK,EAAA;AAAA,EAC/B,aAAa,GAAI,CAAA;AAAA,IACf,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,YAAY,GAAI,CAAA;AAAA,IACd,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,UAAU,GAAI,CAAA;AAAA,IACZ,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,cAAc,GAAI,CAAA;AAAA,IAChB,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,uBAAuB,GAAI,CAAA;AAAA,IACzB,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,yBAAyB,GAAI,CAAA;AAAA,IAC3B,OAAS,EAAA;AAAA,GACV,CAAA;AAAA;AAAA,EAED,kCAAkC,GAAI,CAAA;AAAA,IACpC,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,aAAa,GAAI,CAAA;AAAA,IACf,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,2BAA2B,IAAK,CAAA;AAAA,IAC9B,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,2BAA2B,GAAI,CAAA;AAAA,IAC7B,OAAS,EAAA;AAAA,GACV;AACH,CAAC,CAAA;AACD,SAAS,6BAAwC,GAAA;AAC/C,EAAI,IAAA,UAAA,CAAW,yBAA6B,IAAA,UAAA,CAAW,yBAA2B,EAAA;AAChF,IAAA,OAAO,UAAW,CAAA,yBAAA;AAAA;AAEpB,EAAI,IAAA,UAAA,CAAW,WAAa,EAAA,OAAO,UAAW,CAAA,WAAA;AAC9C,EAAI,IAAA,UAAA,CAAW,aAAoB,OAAA,CAAA,EAAG,WAAW,WAAY,CAAA,OAAA,CAAQ,KAAO,EAAA,EAAE,CAAC,CAAA,QAAA,CAAA;AAC/E,EAAO,OAAA,UAAA;AACT;AACA,SAAS,iBAAiB,UAA4B,EAAA;AACpD,EAAA,MAAM,IAAO,GAAA,UAAA,CAAW,OAAQ,CAAA,YAAA,EAAc,EAAE,CAAA;AAChD,EAAO,OAAA,IAAA,CAAK,OAAQ,CAAA,OAAA,EAAS,IAAI,CAAA;AACnC;AACA,SAAS,yBAAyB,UAA4B,EAAA;AAC5D,EAAO,OAAA,CAAA,EAAG,gBAAiB,CAAA,UAAU,CAAC,CAAA,iBAAA,CAAA;AACxC;AACA,MAAM,yBAAyB,6BAA8B,EAAA;AAC7D,MAAM,uBAA0B,GAAA,IAAA,CAAK,GAAI,CAAA,GAAA,EAAK,IAAK,CAAA,GAAA,CAAI,EAAI,EAAA,IAAA,CAAK,KAAM,CAAA,UAAA,CAAW,gCAAgC,CAAC,CAAC,CAAA;AACtG,MAAA,MAAA,GAAS,iCACjB,UADiB,CAAA,EAAA;AAAA,EAEpB,gCAAkC,EAAA,uBAAA;AAAA,EAClC,yBAA2B,EAAA,sBAAA;AAAA,EAC3B,oBAAA,EAAsB,yBAAyB,sBAAsB;AACvE,CAAA"}
1
+ {"version":3,"file":"env-config.js","sources":["../../src/config/env-config.ts"],"sourcesContent":["import { str, cleanEnv, bool, num } from 'envalid';\n\nconst env = (process as { APP_ENV?: NodeJS.ProcessEnv; env?: NodeJS.ProcessEnv }).APP_ENV || process.env;\n\nconst baseConfig = cleanEnv(env, {\n GRAPHQL_URL: str({ default: 'http://localhost:8080/graphql' }),\n CLIENT_URL: str({ default: 'http://localhost:3000' }),\n APP_NAME: str({ default: 'Yantra' }),\n GROQ_API_KEY: str({ default: '' }),\n CDECLI_AGENT_ENDPOINT: str({ default: 'https://cdecli-agent.cdebase.dev' }),\n CDECLI_AGENT_AUTH_TOKEN: str({ default: '' }),\n /** Seconds to wait for CDeCLI stream completion before timing out (then fallback chat API runs). Min 15, max 600. */\n /** Aligned with web `useCdecliChannel` (120s). Mobile hook uses the same constant inline. */\n CDECLI_CHAT_RESPONSE_TIMEOUT_SEC: num({ default: 120 }),\n BACKEND_URL: str({ default: '' }),\n USE_PROXY_STAGING_GATEWAY: bool({ default: false }),\n PROXY_STAGING_GATEWAY_URL: str({ default: '' }),\n});\n\nfunction resolveAgentGatewayGraphqlUrl(): string {\n if (baseConfig.USE_PROXY_STAGING_GATEWAY && baseConfig.PROXY_STAGING_GATEWAY_URL) {\n return baseConfig.PROXY_STAGING_GATEWAY_URL;\n }\n if (baseConfig.GRAPHQL_URL) return baseConfig.GRAPHQL_URL;\n if (baseConfig.BACKEND_URL) return `${baseConfig.BACKEND_URL.replace(/\\/$/, '')}/graphql`;\n return '/graphql';\n}\n\nfunction resolveBaseWsUrl(graphqlUrl: string): string {\n const base = graphqlUrl.replace(/\\/graphql$/, '');\n return base.replace(/^http/, 'ws');\n}\n\nfunction resolveAgentGatewayWsUrl(graphqlUrl: string): string {\n return `${resolveBaseWsUrl(graphqlUrl)}/ws/openclaw-chat`;\n}\n\nconst agentGatewayGraphqlUrl = resolveAgentGatewayGraphqlUrl();\n\nconst clampedCdecliTimeoutSec = Math.min(600, Math.max(15, Math.round(baseConfig.CDECLI_CHAT_RESPONSE_TIMEOUT_SEC)));\n\nexport const config = {\n ...baseConfig,\n CDECLI_CHAT_RESPONSE_TIMEOUT_SEC: clampedCdecliTimeoutSec,\n AGENT_GATEWAY_GRAPHQL_URL: agentGatewayGraphqlUrl,\n AGENT_GATEWAY_WS_URL: resolveAgentGatewayWsUrl(agentGatewayGraphqlUrl),\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AACA,MAAM,GAAA,GAAO,OAGV,CAAA,OAAA,IAAW,OAAQ,CAAA,GAAA;AACtB,MAAM,UAAA,GAAa,SAAS,GAAK,EAAA;AAAA,EAC/B,aAAa,GAAI,CAAA;AAAA,IACf,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,YAAY,GAAI,CAAA;AAAA,IACd,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,UAAU,GAAI,CAAA;AAAA,IACZ,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,cAAc,GAAI,CAAA;AAAA,IAChB,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,uBAAuB,GAAI,CAAA;AAAA,IACzB,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,yBAAyB,GAAI,CAAA;AAAA,IAC3B,OAAS,EAAA;AAAA,GACV,CAAA;AAAA;AAAA;AAAA,EAGD,kCAAkC,GAAI,CAAA;AAAA,IACpC,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,aAAa,GAAI,CAAA;AAAA,IACf,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,2BAA2B,IAAK,CAAA;AAAA,IAC9B,OAAS,EAAA;AAAA,GACV,CAAA;AAAA,EACD,2BAA2B,GAAI,CAAA;AAAA,IAC7B,OAAS,EAAA;AAAA,GACV;AACH,CAAC,CAAA;AACD,SAAS,6BAAwC,GAAA;AAC/C,EAAI,IAAA,UAAA,CAAW,yBAA6B,IAAA,UAAA,CAAW,yBAA2B,EAAA;AAChF,IAAA,OAAO,UAAW,CAAA,yBAAA;AAAA;AAEpB,EAAI,IAAA,UAAA,CAAW,WAAa,EAAA,OAAO,UAAW,CAAA,WAAA;AAC9C,EAAI,IAAA,UAAA,CAAW,aAAoB,OAAA,CAAA,EAAG,WAAW,WAAY,CAAA,OAAA,CAAQ,KAAO,EAAA,EAAE,CAAC,CAAA,QAAA,CAAA;AAC/E,EAAO,OAAA,UAAA;AACT;AACA,SAAS,iBAAiB,UAA4B,EAAA;AACpD,EAAA,MAAM,IAAO,GAAA,UAAA,CAAW,OAAQ,CAAA,YAAA,EAAc,EAAE,CAAA;AAChD,EAAO,OAAA,IAAA,CAAK,OAAQ,CAAA,OAAA,EAAS,IAAI,CAAA;AACnC;AACA,SAAS,yBAAyB,UAA4B,EAAA;AAC5D,EAAO,OAAA,CAAA,EAAG,gBAAiB,CAAA,UAAU,CAAC,CAAA,iBAAA,CAAA;AACxC;AACA,MAAM,yBAAyB,6BAA8B,EAAA;AAC7D,MAAM,uBAA0B,GAAA,IAAA,CAAK,GAAI,CAAA,GAAA,EAAK,IAAK,CAAA,GAAA,CAAI,EAAI,EAAA,IAAA,CAAK,KAAM,CAAA,UAAA,CAAW,gCAAgC,CAAC,CAAC,CAAA;AACtG,MAAA,MAAA,GAAS,iCACjB,UADiB,CAAA,EAAA;AAAA,EAEpB,gCAAkC,EAAA,uBAAA;AAAA,EAClC,yBAA2B,EAAA,sBAAA;AAAA,EAC3B,oBAAA,EAAsB,yBAAyB,sBAAsB;AACvE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"AudioRecorderPanel.js","sources":["../../../src/features/audio-input/AudioRecorderPanel.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { ActivityIndicator, Animated, Easing, Pressable, StyleSheet, View } from 'react-native';\nimport { Feather, Ionicons } from '@expo/vector-icons';\nimport { RecordingPresets, setAudioModeAsync, useAudioRecorder, useAudioRecorderState } from 'expo-audio';\n// NOTE: AudioModule.requestRecordingPermissionsAsync() + setAudioModeAsync()\n// are called at the screen level via useAudioPermission (see docs pattern),\n// not here. The panel only mounts AFTER permission is already granted.\nimport { transcribeAudio } from '../../api/stt';\n\n/**\n * Mirrors the web ceiling in\n * `packages-modules/account/browser/src/features/audio-input/components/AudioRecorder.tsx`.\n * Three minutes is a soft Whisper ceiling — past that, accuracy and upload\n * time both degrade more than the marginal extra context is worth.\n */\nconst MAX_DURATION_MS = 3 * 60 * 1000;\n\nexport interface AudioRecorderPanelProps {\n isDark: boolean;\n /**\n * Called once with the final transcribed string after the user taps \"Send\".\n * The host screen typically appends this to its composer state and re-focuses\n * the text input — mirroring the web `handleTranscriptionComplete` flow.\n */\n onTranscriptionComplete: (text: string) => void;\n /** Called when the user discards the recording (X) or recording cannot start. */\n onCancel: () => void;\n /** Optional error sink — host can surface a toast/banner. */\n onError?: (error: string) => void;\n}\n\n/**\n * Inline voice-capture panel that replaces the composer while recording —\n * the React Native port of the web `AudioRecorder` component\n * (`packages-modules/account/browser/src/features/audio-input/components/AudioRecorder.tsx`).\n *\n * Flow (parity with web):\n * 1. Mount → request mic permission → set audio mode → prepare + start recording.\n * 2. While recording, display a live timer and pulsing record dot.\n * 3. User taps \"Send\" → stop recorder → upload `recorder.uri` to Groq Whisper\n * via `transcribeAudio` → return the transcribed text to the host via\n * `onTranscriptionComplete`.\n * 4. User taps \"Cancel\" (X) → stop + discard, no upload.\n * 5. If recording exceeds `MAX_DURATION_MS`, auto-finalize as if the user\n * tapped Send (matches web safety net).\n *\n * The panel is intentionally NOT rendered inside the `InputToolBar`'s `topContent`\n * slot — web fully swaps the toolbar for the recorder. Keeping the toolbar\n * visible during recording invites accidental sends with empty input and an\n * unfocused recorder state.\n */\nexport function AudioRecorderPanel({ isDark, onTranscriptionComplete, onCancel, onError }: AudioRecorderPanelProps) {\n const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);\n const recorderState = useAudioRecorderState(recorder, 200);\n const [isTranscribing, setIsTranscribing] = useState(false);\n\n /**\n * Guard against re-entry. `useEffect` runs once on mount but a fast double-\n * tap on the mic can produce two panel mounts in dev mode (StrictMode);\n * `hasStartedRef` ensures we don't start a second recorder against the\n * same shared object.\n */\n const hasStartedRef = useRef(false);\n const isMountedRef = useRef(true);\n /**\n * Caches the result of the in-flight transcription/cleanup pipeline so the\n * MAX_DURATION watcher and the explicit Send button can both call\n * `finalize()` without racing each other into a double-upload.\n */\n const sendInFlightRef = useRef(false);\n\n /**\n * Pulsing dot for the \"REC\" indicator. Pure UI sugar; avoids importing\n * reanimated for a 2-keyframe loop.\n */\n const pulse = useRef(new Animated.Value(1)).current;\n useEffect(() => {\n const loop = Animated.loop(\n Animated.sequence([\n Animated.timing(pulse, {\n toValue: 0.3,\n duration: 700,\n easing: Easing.inOut(Easing.ease),\n useNativeDriver: true,\n }),\n Animated.timing(pulse, {\n toValue: 1,\n duration: 700,\n easing: Easing.inOut(Easing.ease),\n useNativeDriver: true,\n }),\n ]),\n );\n loop.start();\n return () => loop.stop();\n }, [pulse]);\n\n const stopAndCleanup = useCallback(async () => {\n try {\n if (recorderState.isRecording || recorder.isRecording) {\n await recorder.stop().catch(() => undefined);\n }\n } catch {\n /* best-effort */\n }\n try {\n /*\n * Releasing the recording audio session matters more on iOS — leaving\n * `allowsRecording: true` will dim playback for the rest of the app\n * lifecycle. We tolerate failures because the AVAudioSession may\n * already be torn down by the time we get here.\n */\n await setAudioModeAsync({ allowsRecording: false, playsInSilentMode: true });\n } catch {\n /* best-effort */\n }\n }, [recorder, recorderState.isRecording]);\n\n /**\n * One-shot finalize: stop → upload → emit transcript. Swallowed errors get\n * surfaced via `onError` so the host can show a toast. We always clear\n * `sendInFlightRef` even on error so a retry path is possible if needed.\n */\n const finalize = useCallback(async () => {\n if (sendInFlightRef.current || isTranscribing) return;\n sendInFlightRef.current = true;\n if (isMountedRef.current) setIsTranscribing(true);\n try {\n await stopAndCleanup();\n const uri = recorder.uri;\n if (!uri) {\n throw new Error('No recording produced');\n }\n const text = await transcribeAudio({ uri, mimeType: 'audio/m4a', filename: 'audio.m4a' });\n if (isMountedRef.current) {\n onTranscriptionComplete(text);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n onError?.(msg);\n onCancel();\n } finally {\n sendInFlightRef.current = false;\n if (isMountedRef.current) setIsTranscribing(false);\n }\n }, [isTranscribing, recorder, stopAndCleanup, onTranscriptionComplete, onError, onCancel]);\n\n useEffect(() => {\n if (hasStartedRef.current) return;\n hasStartedRef.current = true;\n let cancelled = false;\n const start = async () => {\n try {\n /*\n * Permission + audio mode are already configured by the host\n * screen via `useAudioPermission()` (follows the official\n * expo-audio v53 docs pattern: request permission + set audio\n * mode once at screen mount). The panel only mounts after\n * `micReady === true`, so we go straight to prepare + record.\n */\n await recorder.prepareToRecordAsync();\n if (cancelled || !isMountedRef.current) return;\n recorder.record();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n onError?.(msg);\n onCancel();\n }\n };\n void start();\n return () => {\n cancelled = true;\n };\n // Mount-only effect: recorder/start refs are stable for the life of the panel.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n return () => {\n isMountedRef.current = false;\n /*\n * Unmount cleanup: fire-and-forget. We don't await anything here\n * because React doesn't allow async destructors and we can't\n * guarantee the recorder shared object outlives this tick. The\n * stopAndCleanup util is null-safe.\n */\n void stopAndCleanup();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n /**\n * Max-duration safety net. `RecorderState.durationMillis` already reports\n * elapsed time in milliseconds (see `expo-audio` `Audio.types.ts`), which\n * lines up directly with our `MAX_DURATION_MS` ceiling (web parity).\n */\n useEffect(() => {\n if (!recorderState.isRecording) return;\n const elapsedMs = recorderState.durationMillis ?? 0;\n if (elapsedMs >= MAX_DURATION_MS && !sendInFlightRef.current) {\n void finalize();\n }\n }, [recorderState.isRecording, recorderState.durationMillis, finalize]);\n\n const handleCancelPress = useCallback(async () => {\n if (isTranscribing) return;\n await stopAndCleanup();\n onCancel();\n }, [isTranscribing, stopAndCleanup, onCancel]);\n\n const handleSendPress = useCallback(() => {\n void finalize();\n }, [finalize]);\n\n const formatted = useMemo(() => {\n const totalSeconds = Math.floor((recorderState.durationMillis ?? 0) / 1000);\n const m = Math.floor(totalSeconds / 60);\n const s = totalSeconds % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n }, [recorderState.durationMillis]);\n\n const bg = isDark ? '#0f172a' : '#ffffff';\n const border = isDark ? 'rgba(148,163,184,0.18)' : '#e4e4e7';\n const fg = isDark ? '#e5e7eb' : '#18181b';\n const muted = isDark ? '#94a3b8' : '#71717a';\n const cancelBg = isDark ? 'rgba(148,163,184,0.08)' : '#f4f4f5';\n\n const showSpinner = isTranscribing;\n const isRecording = recorderState.isRecording && !showSpinner;\n const statusLabel = showSpinner ? 'Transcribing…' : isRecording ? formatted : 'Initializing…';\n\n return (\n <View\n accessibilityRole=\"summary\"\n accessibilityLabel=\"Voice recorder\"\n style={[styles.container, { backgroundColor: bg, borderColor: border }]}\n >\n <View style={styles.statusRow}>\n {showSpinner ? (\n <ActivityIndicator size=\"small\" color={fg} />\n ) : (\n <Animated.View style={[styles.recDot, { opacity: isRecording ? pulse : 0.35 }]} />\n )}\n <Animated.Text style={[styles.timer, { color: fg }]} accessibilityLiveRegion=\"polite\">\n {statusLabel}\n </Animated.Text>\n {isRecording && <Animated.Text style={[styles.maxLabel, { color: muted }]}>/ 3:00</Animated.Text>}\n </View>\n <View style={styles.actionsRow}>\n <Pressable\n onPress={handleCancelPress}\n disabled={isTranscribing}\n accessibilityLabel=\"Cancel recording\"\n accessibilityRole=\"button\"\n style={({ pressed }) => [\n styles.iconBtn,\n {\n backgroundColor: cancelBg,\n borderColor: border,\n opacity: isTranscribing ? 0.4 : pressed ? 0.7 : 1,\n },\n ]}\n >\n <Ionicons name=\"close\" size={16} color={fg} />\n </Pressable>\n <Pressable\n onPress={handleSendPress}\n disabled={isTranscribing || !isRecording}\n accessibilityLabel=\"Stop and transcribe\"\n accessibilityRole=\"button\"\n style={({ pressed }) => [\n styles.sendBtn,\n {\n opacity: isTranscribing ? 0.7 : !isRecording ? 0.5 : pressed ? 0.85 : 1,\n },\n ]}\n >\n {isTranscribing ? (\n <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n ) : (\n <Feather name=\"arrow-up\" size={16} color=\"#ffffff\" />\n )}\n </Pressable>\n </View>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n borderRadius: 24,\n borderWidth: 1,\n paddingVertical: 10,\n paddingHorizontal: 14,\n gap: 12,\n shadowColor: '#0f172a',\n shadowOpacity: 0.08,\n shadowRadius: 12,\n shadowOffset: { width: 0, height: 4 },\n elevation: 4,\n },\n statusRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n flexShrink: 1,\n },\n recDot: {\n width: 8,\n height: 8,\n borderRadius: 4,\n backgroundColor: '#ef4444',\n },\n timer: {\n fontSize: 13,\n fontWeight: '600',\n fontVariant: ['tabular-nums'],\n },\n maxLabel: {\n fontSize: 11,\n },\n actionsRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n },\n iconBtn: {\n width: 32,\n height: 32,\n borderRadius: 16,\n borderWidth: 1,\n alignItems: 'center',\n justifyContent: 'center',\n },\n sendBtn: {\n width: 32,\n height: 32,\n borderRadius: 16,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: '#18181b',\n },\n});\n\nexport default AudioRecorderPanel;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AudioRecorderPanel.js","sources":["../../../src/features/audio-input/AudioRecorderPanel.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { ActivityIndicator, Animated, Easing, Pressable, StyleSheet, View } from 'react-native';\nimport { Feather, Ionicons } from '@expo/vector-icons';\nimport { RecordingPresets, setAudioModeAsync, useAudioRecorder, useAudioRecorderState } from 'expo-audio';\nimport { transcribeAudio } from '../../api/stt';\n\n/**\n * Mirrors the web ceiling in\n * `packages-modules/account/browser/src/features/audio-input/components/AudioRecorder.tsx`.\n * Three minutes is a soft Whisper ceiling — past that, accuracy and upload\n * time both degrade more than the marginal extra context is worth.\n */\nconst MAX_DURATION_MS = 3 * 60 * 1000;\n\nexport interface AudioRecorderPanelProps {\n isDark: boolean;\n /**\n * Called once with the final transcribed string after the user taps \"Send\".\n * The host screen typically appends this to its composer state and re-focuses\n * the text input — mirroring the web `handleTranscriptionComplete` flow.\n */\n onTranscriptionComplete: (text: string) => void;\n /** Called when the user discards the recording (X) or recording cannot start. */\n onCancel: () => void;\n /** Optional error sink — host can surface a toast/banner. */\n onError?: (error: string) => void;\n}\n\n/**\n * Inline voice-capture panel that replaces the composer while recording —\n * the React Native port of the web `AudioRecorder` component\n * (`packages-modules/account/browser/src/features/audio-input/components/AudioRecorder.tsx`).\n *\n * Flow (parity with web):\n * 1. Mount → request mic permission → set audio mode → prepare + start recording.\n * 2. While recording, display a live timer and pulsing record dot.\n * 3. User taps \"Send\" → stop recorder → upload `recorder.uri` to Groq Whisper\n * via `transcribeAudio` → return the transcribed text to the host via\n * `onTranscriptionComplete`.\n * 4. User taps \"Cancel\" (X) → stop + discard, no upload.\n * 5. If recording exceeds `MAX_DURATION_MS`, auto-finalize as if the user\n * tapped Send (matches web safety net).\n *\n * The panel is intentionally NOT rendered inside the `InputToolBar`'s `topContent`\n * slot — web fully swaps the toolbar for the recorder. Keeping the toolbar\n * visible during recording invites accidental sends with empty input and an\n * unfocused recorder state.\n */\nexport function AudioRecorderPanel({ isDark, onTranscriptionComplete, onCancel, onError }: AudioRecorderPanelProps) {\n const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);\n const recorderState = useAudioRecorderState(recorder, 200);\n const [isTranscribing, setIsTranscribing] = useState(false);\n\n /**\n * Guard against re-entry. `useEffect` runs once on mount but a fast double-\n * tap on the mic can produce two panel mounts in dev mode (StrictMode);\n * `hasStartedRef` ensures we don't start a second recorder against the\n * same shared object.\n */\n const hasStartedRef = useRef(false);\n const isMountedRef = useRef(true);\n /**\n * Caches the result of the in-flight transcription/cleanup pipeline so the\n * MAX_DURATION watcher and the explicit Send button can both call\n * `finalize()` without racing each other into a double-upload.\n */\n const sendInFlightRef = useRef(false);\n\n /**\n * Pulsing dot for the \"REC\" indicator. Pure UI sugar; avoids importing\n * reanimated for a 2-keyframe loop.\n */\n const pulse = useRef(new Animated.Value(1)).current;\n useEffect(() => {\n const loop = Animated.loop(\n Animated.sequence([\n Animated.timing(pulse, {\n toValue: 0.3,\n duration: 700,\n easing: Easing.inOut(Easing.ease),\n useNativeDriver: true,\n }),\n Animated.timing(pulse, {\n toValue: 1,\n duration: 700,\n easing: Easing.inOut(Easing.ease),\n useNativeDriver: true,\n }),\n ]),\n );\n loop.start();\n return () => loop.stop();\n }, [pulse]);\n\n const stopAndCleanup = useCallback(async () => {\n try {\n if (recorderState.isRecording || recorder.isRecording) {\n await recorder.stop().catch(() => undefined);\n }\n } catch {\n /* best-effort */\n }\n try {\n /*\n * Releasing the recording audio session matters more on iOS — leaving\n * `allowsRecording: true` will dim playback for the rest of the app\n * lifecycle. We tolerate failures because the AVAudioSession may\n * already be torn down by the time we get here.\n */\n await setAudioModeAsync({ allowsRecording: false, playsInSilentMode: true });\n } catch {\n /* best-effort */\n }\n }, [recorder, recorderState.isRecording]);\n\n /**\n * One-shot finalize: stop → upload → emit transcript. Swallowed errors get\n * surfaced via `onError` so the host can show a toast. We always clear\n * `sendInFlightRef` even on error so a retry path is possible if needed.\n */\n const finalize = useCallback(async () => {\n if (sendInFlightRef.current || isTranscribing) return;\n sendInFlightRef.current = true;\n if (isMountedRef.current) setIsTranscribing(true);\n try {\n await stopAndCleanup();\n const uri = recorder.uri;\n if (!uri) {\n throw new Error('No recording produced');\n }\n const text = await transcribeAudio({ uri, mimeType: 'audio/m4a', filename: 'audio.m4a' });\n if (isMountedRef.current) {\n onTranscriptionComplete(text);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n onError?.(msg);\n onCancel();\n } finally {\n sendInFlightRef.current = false;\n if (isMountedRef.current) setIsTranscribing(false);\n }\n }, [isTranscribing, recorder, stopAndCleanup, onTranscriptionComplete, onError, onCancel]);\n\n useEffect(() => {\n if (hasStartedRef.current) return;\n hasStartedRef.current = true;\n let cancelled = false;\n const start = async () => {\n try {\n /*\n * Host screen already called `requestMicPermission()` (expo-audio\n * docs flow) before mounting this panel — go straight to\n * prepare + record.\n */\n await recorder.prepareToRecordAsync();\n if (cancelled || !isMountedRef.current) return;\n recorder.record();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n onError?.(msg);\n onCancel();\n }\n };\n void start();\n return () => {\n cancelled = true;\n };\n // Mount-only effect: recorder/start refs are stable for the life of the panel.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n return () => {\n isMountedRef.current = false;\n /*\n * Unmount cleanup: fire-and-forget. We don't await anything here\n * because React doesn't allow async destructors and we can't\n * guarantee the recorder shared object outlives this tick. The\n * stopAndCleanup util is null-safe.\n */\n void stopAndCleanup();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n /**\n * Max-duration safety net. `RecorderState.durationMillis` already reports\n * elapsed time in milliseconds (see `expo-audio` `Audio.types.ts`), which\n * lines up directly with our `MAX_DURATION_MS` ceiling (web parity).\n */\n useEffect(() => {\n if (!recorderState.isRecording) return;\n const elapsedMs = recorderState.durationMillis ?? 0;\n if (elapsedMs >= MAX_DURATION_MS && !sendInFlightRef.current) {\n void finalize();\n }\n }, [recorderState.isRecording, recorderState.durationMillis, finalize]);\n\n const handleCancelPress = useCallback(async () => {\n if (isTranscribing) return;\n await stopAndCleanup();\n onCancel();\n }, [isTranscribing, stopAndCleanup, onCancel]);\n\n const handleSendPress = useCallback(() => {\n void finalize();\n }, [finalize]);\n\n const formatted = useMemo(() => {\n const totalSeconds = Math.floor((recorderState.durationMillis ?? 0) / 1000);\n const m = Math.floor(totalSeconds / 60);\n const s = totalSeconds % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n }, [recorderState.durationMillis]);\n\n const bg = isDark ? '#0f172a' : '#ffffff';\n const border = isDark ? 'rgba(148,163,184,0.18)' : '#e4e4e7';\n const fg = isDark ? '#e5e7eb' : '#18181b';\n const muted = isDark ? '#94a3b8' : '#71717a';\n const cancelBg = isDark ? 'rgba(148,163,184,0.08)' : '#f4f4f5';\n\n const showSpinner = isTranscribing;\n const isRecording = recorderState.isRecording && !showSpinner;\n const statusLabel = showSpinner ? 'Transcribing…' : isRecording ? formatted : 'Initializing…';\n\n return (\n <View\n accessibilityRole=\"summary\"\n accessibilityLabel=\"Voice recorder\"\n style={[styles.container, { backgroundColor: bg, borderColor: border }]}\n >\n <View style={styles.statusRow}>\n {showSpinner ? (\n <ActivityIndicator size=\"small\" color={fg} />\n ) : (\n <Animated.View style={[styles.recDot, { opacity: isRecording ? pulse : 0.35 }]} />\n )}\n <Animated.Text style={[styles.timer, { color: fg }]} accessibilityLiveRegion=\"polite\">\n {statusLabel}\n </Animated.Text>\n {isRecording && <Animated.Text style={[styles.maxLabel, { color: muted }]}>/ 3:00</Animated.Text>}\n </View>\n <View style={styles.actionsRow}>\n <Pressable\n onPress={handleCancelPress}\n disabled={isTranscribing}\n accessibilityLabel=\"Cancel recording\"\n accessibilityRole=\"button\"\n style={({ pressed }) => [\n styles.iconBtn,\n {\n backgroundColor: cancelBg,\n borderColor: border,\n opacity: isTranscribing ? 0.4 : pressed ? 0.7 : 1,\n },\n ]}\n >\n <Ionicons name=\"close\" size={16} color={fg} />\n </Pressable>\n <Pressable\n onPress={handleSendPress}\n disabled={isTranscribing || !isRecording}\n accessibilityLabel=\"Stop and transcribe\"\n accessibilityRole=\"button\"\n style={({ pressed }) => [\n styles.sendBtn,\n {\n opacity: isTranscribing ? 0.7 : !isRecording ? 0.5 : pressed ? 0.85 : 1,\n },\n ]}\n >\n {isTranscribing ? (\n <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n ) : (\n <Feather name=\"arrow-up\" size={16} color=\"#ffffff\" />\n )}\n </Pressable>\n </View>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n borderRadius: 24,\n borderWidth: 1,\n paddingVertical: 10,\n paddingHorizontal: 14,\n gap: 12,\n shadowColor: '#0f172a',\n shadowOpacity: 0.08,\n shadowRadius: 12,\n shadowOffset: { width: 0, height: 4 },\n elevation: 4,\n },\n statusRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n flexShrink: 1,\n },\n recDot: {\n width: 8,\n height: 8,\n borderRadius: 4,\n backgroundColor: '#ef4444',\n },\n timer: {\n fontSize: 13,\n fontWeight: '600',\n fontVariant: ['tabular-nums'],\n },\n maxLabel: {\n fontSize: 11,\n },\n actionsRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n },\n iconBtn: {\n width: 32,\n height: 32,\n borderRadius: 16,\n borderWidth: 1,\n alignItems: 'center',\n justifyContent: 'center',\n },\n sendBtn: {\n width: 32,\n height: 32,\n borderRadius: 16,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: '#18181b',\n },\n});\n\nexport default AudioRecorderPanel;\n"],"names":[],"mappings":"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"}
@@ -1,36 +1,24 @@
1
- import {useState,useEffect}from'react';import {AudioModule,setAudioModeAsync}from'expo-audio';function useAudioPermission() {
2
- const [micReady, setMicReady] = useState(false);
3
- const [micError, setMicError] = useState(null);
4
- useEffect(() => {
5
- let cancelled = false;
6
- (async () => {
7
- try {
8
- const status = await AudioModule.requestRecordingPermissionsAsync();
9
- if (cancelled) return;
10
- if (!status.granted) {
11
- setMicError("Microphone permission is required for voice input.");
12
- return;
13
- }
14
- await setAudioModeAsync({
15
- playsInSilentMode: true,
16
- allowsRecording: true
17
- });
18
- if (!cancelled) {
19
- setMicReady(true);
20
- }
21
- } catch (err) {
22
- if (!cancelled) {
23
- const msg = err instanceof Error ? err.message : String(err);
24
- setMicError(msg);
25
- }
26
- }
27
- })();
28
- return () => {
29
- cancelled = true;
1
+ import {AudioModule,setAudioModeAsync}from'expo-audio';async function requestMicPermission() {
2
+ try {
3
+ const status = await AudioModule.requestRecordingPermissionsAsync();
4
+ if (!status.granted) {
5
+ return {
6
+ granted: false,
7
+ error: "Microphone permission is required for voice input."
8
+ };
9
+ }
10
+ await setAudioModeAsync({
11
+ playsInSilentMode: true,
12
+ allowsRecording: true
13
+ });
14
+ return {
15
+ granted: true
30
16
  };
31
- }, []);
32
- return {
33
- micReady,
34
- micError
35
- };
36
- }export{useAudioPermission};//# sourceMappingURL=useAudioPermission.js.map
17
+ } catch (err) {
18
+ const msg = err instanceof Error ? err.message : String(err);
19
+ return {
20
+ granted: false,
21
+ error: msg
22
+ };
23
+ }
24
+ }export{requestMicPermission};//# sourceMappingURL=useAudioPermission.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAudioPermission.js","sources":["../../../src/features/audio-input/useAudioPermission.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { AudioModule, setAudioModeAsync } from 'expo-audio';\n\n/**\n * Follows the official expo-audio v53 recording example exactly:\n *\n * useEffect(() => {\n * (async () => {\n * const status = await AudioModule.requestRecordingPermissionsAsync();\n * if (!status.granted) {\n * Alert.alert('Permission to access microphone was denied');\n * }\n * setAudioModeAsync({ playsInSilentMode: true, allowsRecording: true });\n * })();\n * }, []);\n *\n * This hook runs **once at screen mount** (Home / Chat), NOT inside the\n * conditionally-rendered AudioRecorderPanel. Requesting permission at the\n * screen level means the native `AudioRecordingRequester` fires before the\n * `useAudioRecorder` hook ever constructs a native `AVAudioRecorder` object,\n * which avoids the SIGABRT crash observed in TestFlight when the two race on\n * the expo-modules dispatch queue.\n *\n * After the hook completes, the screen can safely check `micReady` before\n * mounting the panel. If permission was denied, `micError` carries the reason\n * for the host screen's error banner.\n */\nexport function useAudioPermission() {\n const [micReady, setMicReady] = useState(false);\n const [micError, setMicError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n (async () => {\n try {\n const status = await AudioModule.requestRecordingPermissionsAsync();\n if (cancelled) return;\n if (!status.granted) {\n setMicError('Microphone permission is required for voice input.');\n return;\n }\n await setAudioModeAsync({\n playsInSilentMode: true,\n allowsRecording: true,\n });\n if (!cancelled) {\n setMicReady(true);\n }\n } catch (err) {\n if (!cancelled) {\n const msg = err instanceof Error ? err.message : String(err);\n setMicError(msg);\n }\n }\n })();\n return () => {\n cancelled = true;\n };\n }, []);\n\n return { micReady, micError };\n}\n"],"names":[],"mappings":"8FA2BO,SAAS,kBAAqB,GAAA;AACnC,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,KAAK,CAAA;AAC9C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAwB,IAAI,CAAA;AAC5D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAY,GAAA,KAAA;AAChB,IAAA,CAAC,YAAY;AACX,MAAI,IAAA;AACF,QAAM,MAAA,MAAA,GAAS,MAAM,WAAA,CAAY,gCAAiC,EAAA;AAClE,QAAA,IAAI,SAAW,EAAA;AACf,QAAI,IAAA,CAAC,OAAO,OAAS,EAAA;AACnB,UAAA,WAAA,CAAY,oDAAoD,CAAA;AAChE,UAAA;AAAA;AAEF,QAAA,MAAM,iBAAkB,CAAA;AAAA,UACtB,iBAAmB,EAAA,IAAA;AAAA,UACnB,eAAiB,EAAA;AAAA,SAClB,CAAA;AACD,QAAA,IAAI,CAAC,SAAW,EAAA;AACd,UAAA,WAAA,CAAY,IAAI,CAAA;AAAA;AAClB,eACO,GAAK,EAAA;AACZ,QAAA,IAAI,CAAC,SAAW,EAAA;AACd,UAAA,MAAM,MAAM,GAAe,YAAA,KAAA,GAAQ,GAAI,CAAA,OAAA,GAAU,OAAO,GAAG,CAAA;AAC3D,UAAA,WAAA,CAAY,GAAG,CAAA;AAAA;AACjB;AACF,KACC,GAAA;AACH,IAAA,OAAO,MAAM;AACX,MAAY,SAAA,GAAA,IAAA;AAAA,KACd;AAAA,GACF,EAAG,EAAE,CAAA;AACL,EAAO,OAAA;AAAA,IACL,QAAA;AAAA,IACA;AAAA,GACF;AACF"}
1
+ {"version":3,"file":"useAudioPermission.js","sources":["../../../src/features/audio-input/useAudioPermission.ts"],"sourcesContent":["import { AudioModule, setAudioModeAsync } from 'expo-audio';\n\nexport type MicPermissionResult = {\n granted: boolean;\n error?: string;\n};\n\n/**\n * Official expo-audio v53 recording flow called when the user taps the mic\n * (user-initiated), not at screen mount:\n *\n * 1. AudioModule.requestRecordingPermissionsAsync()\n * 2. setAudioModeAsync({ playsInSilentMode: true, allowsRecording: true })\n *\n * `expo-audio`'s native `record()` checks permission state set by step 1.\n * iOS `setAudioModeAsync` alone can show the system dialog but does NOT update\n * expo-audio's internal grant flag `AudioPermissionsException` on `record()`.\n *\n * Requires `NSMicrophoneUsageDescription` in the built Info.plist (expo-audio\n * plugin + ios.infoPlist). Without it, step 1 can native-crash in older builds.\n */\nexport async function requestMicPermission(): Promise<MicPermissionResult> {\n try {\n const status = await AudioModule.requestRecordingPermissionsAsync();\n if (!status.granted) {\n return {\n granted: false,\n error: 'Microphone permission is required for voice input.',\n };\n }\n await setAudioModeAsync({\n playsInSilentMode: true,\n allowsRecording: true,\n });\n return { granted: true };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { granted: false, error: msg };\n }\n}\n"],"names":[],"mappings":"uDAoBA,eAAsB,oBAAqD,GAAA;AACzE,EAAI,IAAA;AACF,IAAM,MAAA,MAAA,GAAS,MAAM,WAAA,CAAY,gCAAiC,EAAA;AAClE,IAAI,IAAA,CAAC,OAAO,OAAS,EAAA;AACnB,MAAO,OAAA;AAAA,QACL,OAAS,EAAA,KAAA;AAAA,QACT,KAAO,EAAA;AAAA,OACT;AAAA;AAEF,IAAA,MAAM,iBAAkB,CAAA;AAAA,MACtB,iBAAmB,EAAA,IAAA;AAAA,MACnB,eAAiB,EAAA;AAAA,KAClB,CAAA;AACD,IAAO,OAAA;AAAA,MACL,OAAS,EAAA;AAAA,KACX;AAAA,WACO,GAAK,EAAA;AACZ,IAAA,MAAM,MAAM,GAAe,YAAA,KAAA,GAAQ,GAAI,CAAA,OAAA,GAAU,OAAO,GAAG,CAAA;AAC3D,IAAO,OAAA;AAAA,MACL,OAAS,EAAA,KAAA;AAAA,MACT,KAAO,EAAA;AAAA,KACT;AAAA;AAEJ"}
@@ -1,4 +1,4 @@
1
- import {useState,useRef,useCallback,useEffect}from'react';import {useGatewayConnectMutation,useGatewayDisconnectMutation,useCreateSecretApiTokenMutation,useRevealSecretApiTokenMutation,useGetUserSystemTokenQuery}from'common/graphql';import {config}from'../config/env-config.js';import {usePrerequisiteIds}from'./usePrerequisiteIds.js';var __defProp = Object.defineProperty;
1
+ import {useState,useRef,useCallback,useEffect}from'react';import {useGatewayConnectMutation,useGatewayDisconnectMutation,useCreateSecretApiTokenMutation,useRevealSecretApiTokenMutation,useGetUserSystemTokenQuery}from'common/graphql';import {SecretApiTokenAlgorithm}from'common';import {config}from'../config/env-config.js';import {usePrerequisiteIds}from'./usePrerequisiteIds.js';var __defProp = Object.defineProperty;
2
2
  var __defProps = Object.defineProperties;
3
3
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
@@ -17,6 +17,7 @@ var __spreadValues = (a, b) => {
17
17
  return a;
18
18
  };
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ const SYSTEM_TOKEN_ALGORITHM = __DEV__ ? SecretApiTokenAlgorithm.Hs256 : SecretApiTokenAlgorithm.Rs256;
20
21
  const CHANNEL_TYPE = "cdecli-serve";
21
22
  function useCdecliAutoConnect(isSelected, channelId) {
22
23
  const [status, setStatus] = useState("idle");
@@ -71,7 +72,8 @@ function useCdecliAutoConnect(isSelected, channelId) {
71
72
  isSystemToken: true,
72
73
  environmentTagId: tagId,
73
74
  orgName: orgName || "default",
74
- projectId: projectId || "default"
75
+ projectId: projectId || "default",
76
+ algorithm: SYSTEM_TOKEN_ALGORITHM
75
77
  }
76
78
  }
77
79
  });
@@ -1 +1 @@
1
- {"version":3,"file":"useCdecliAutoConnect.js","sources":["../../src/hooks/useCdecliAutoConnect.ts"],"sourcesContent":["/**\n * Auto-connects the cdecli-serve messenger-gateway channel when the CDeCLI\n * gateway is selected from the dropdown. Obtains/creates a system token and\n * calls the `gatewayConnect` mutation so the backend provider has a live\n * session with the cdecli-agent endpoint.\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n useGatewayConnectMutation,\n useGatewayDisconnectMutation,\n useGetUserSystemTokenQuery,\n useCreateSecretApiTokenMutation,\n useRevealSecretApiTokenMutation,\n} from 'common/graphql';\nimport { config } from '../config/env-config';\nimport { usePrerequisiteIds } from './usePrerequisiteIds';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport type CdecliAutoConnectStatus = 'idle' | 'connecting' | 'connected' | 'error';\n\nexport interface UseCdecliAutoConnectResult {\n /** Whether the backend cdecli-serve channel is connected. */\n channelConnected: boolean;\n /** Current connection status. */\n status: CdecliAutoConnectStatus;\n /** Error message if status is 'error'. */\n error: string | null;\n /** Persistence mode reported by the backend provider ('backend' | 'frontend'). */\n persistenceMode: 'backend' | 'frontend' | undefined;\n /** Active skill name on the current CDeCLI session (null if none). */\n activeSkill: string | null;\n /** Per-user accountId used for this gateway connection. */\n accountId: string;\n /** Reconnect the CDeCLI session with a specific skill pre-activated. */\n connectWithSkill: (skill: string, model?: string, systemPrompt?: string, skillId?: string) => Promise<void>;\n}\n\n// ─── Constants ───────────────────────────────────────────────────\n\nconst CHANNEL_TYPE = 'cdecli-serve';\n\n// ─── Hook ────────────────────────────────────────────────────────\n\n/**\n * When `isSelected` is true, auto-connects the cdecli-serve channel on the\n * messenger-gateway backend using the hosted endpoint and a system token.\n */\nexport function useCdecliAutoConnect(isSelected: boolean, channelId?: string): UseCdecliAutoConnectResult {\n const [status, setStatus] = useState<CdecliAutoConnectStatus>('idle');\n const [error, setError] = useState<string | null>(null);\n const [persistenceMode, setPersistenceMode] = useState<'backend' | 'frontend' | undefined>(undefined);\n const [activeSkill, setActiveSkill] = useState<string | null>(null);\n const connectingRef = useRef(false);\n const connectedRef = useRef(false);\n\n const { orgName, projectId, tagId, accountUserId, loading: prerequisitesLoading } = usePrerequisiteIds();\n\n // Per-user accountId so each user gets their own cdecli-agent session.\n // Wait for the profile query to load before connecting — otherwise all\n // users fall back to 'default' and share a single cdecli-agent session.\n const accountId = accountUserId ?? 'default';\n\n // Track the previous channelId so we can reconnect when the user opens a new chat.\n const prevChannelIdRef = useRef<string | undefined>(channelId);\n\n const [connectMutation] = useGatewayConnectMutation();\n const [disconnectMutation] = useGatewayDisconnectMutation();\n const [createTokenMutation] = useCreateSecretApiTokenMutation();\n const [revealTokenMutation] = useRevealSecretApiTokenMutation();\n const {\n data: systemTokenData,\n loading: systemTokenLoading,\n refetch: refetchSystemToken,\n } = useGetUserSystemTokenQuery({\n skip: !isSelected,\n fetchPolicy: 'network-only',\n });\n\n /** True when the backend rejects createSecretApiToken because the user already has one. */\n const isAlreadyHasSystemTokenError = (err: unknown): boolean => {\n const message = err instanceof Error ? err.message : String(err ?? '');\n return /already have a system token/i.test(message);\n };\n\n const obtainSystemToken = useCallback(async (): Promise<string | null> => {\n try {\n let tokenId: string | null = systemTokenData?.getUserSystemToken?.id ?? null;\n\n /**\n * Race recovery: if the GetUserSystemToken query hasn't returned yet (or returned\n * stale data) we'll fall through to createSecretApiToken. The backend rejects that\n * with \"you already have a system token\" — catch it, refetch, and use the existing\n * token id instead of falling back to an unauthenticated connection.\n */\n if (!tokenId) {\n if (!orgName || !projectId) {\n const refetchResult = await refetchSystemToken().catch(() => null);\n tokenId = refetchResult?.data?.getUserSystemToken?.id ?? null;\n if (!tokenId) return null;\n } else {\n try {\n const { data: createData } = await createTokenMutation({\n variables: {\n input: {\n name: 'System Token',\n expiryInSeconds: 31536000,\n isSystemToken: true,\n environmentTagId: tagId,\n orgName: orgName || 'default',\n projectId: projectId || 'default',\n },\n },\n });\n tokenId = createData?.createSecretApiToken?.id ?? null;\n await refetchSystemToken();\n } catch (createErr) {\n if (!isAlreadyHasSystemTokenError(createErr)) throw createErr;\n const refetchResult = await refetchSystemToken().catch(() => null);\n tokenId = refetchResult?.data?.getUserSystemToken?.id ?? null;\n if (!tokenId) {\n console.warn(\n '[useCdecliAutoConnect] System token already exists but refetch returned no id; retrying',\n );\n return null;\n }\n }\n }\n }\n\n if (!tokenId) return null;\n\n const { data: revealData } = await revealTokenMutation({\n variables: { id: tokenId },\n });\n return (revealData?.revealSecretApiToken as string) || null;\n } catch (err) {\n console.error('[useCdecliAutoConnect] obtainSystemToken error:', err);\n return null;\n }\n }, [systemTokenData, createTokenMutation, revealTokenMutation, refetchSystemToken, orgName, projectId, tagId]);\n\n // Auto-connect when CDeCLI gateway is selected\n useEffect(() => {\n if (!isSelected) {\n // Reset when deselected, but don't disconnect — keep the session alive\n // so switching back is instant.\n connectingRef.current = false;\n return;\n }\n\n // Wait until the user profile has loaded so accountId is the real\n // user id instead of the 'default' fallback.\n if (prerequisitesLoading || !accountUserId) return;\n\n /**\n * Wait for the GetUserSystemToken query to resolve before we connect — otherwise we\n * race the network and try to create a duplicate token, which the backend rejects with\n * \"you already have a system token\" and we fall back to an unauthenticated session.\n */\n if (systemTokenLoading) return;\n\n /**\n * If we're already connected and just switching channels, the gateway needs a reconnect\n * (the session is bound server-side to chatId), but the user already had a working\n * connection. Skip flipping the visible status to 'connecting' so the toolbar pill\n * doesn't briefly show \"Checking...\" every time someone taps a history row.\n */\n let isSilentReconnect = false;\n if (connectedRef.current || connectingRef.current) {\n if (channelId && prevChannelIdRef.current !== channelId) {\n isSilentReconnect = connectedRef.current;\n prevChannelIdRef.current = channelId;\n connectedRef.current = false;\n } else {\n return;\n }\n }\n\n const doConnect = async () => {\n connectingRef.current = true;\n if (!isSilentReconnect) setStatus('connecting');\n setError(null);\n\n try {\n const endpoint = config.CDECLI_AGENT_ENDPOINT || 'https://cdecli-agent.cdebase.dev';\n const token = await obtainSystemToken();\n if (!token) {\n console.warn('[useCdecliAutoConnect] No system token, connecting without auth');\n }\n\n const { data } = await connectMutation({\n variables: {\n input: {\n channelType: CHANNEL_TYPE,\n accountId,\n options: {\n endpoint: endpoint.trim(),\n ...(token ? { token } : {}),\n ...(channelId ? { chatId: channelId } : {}),\n },\n },\n },\n });\n\n const result = data?.gatewayConnect as\n | ((typeof data)['gatewayConnect'] & { persistenceMode?: string })\n | undefined;\n console.log(\n '[useCdecliAutoConnect] connectMutation result (account=%s):',\n accountId,\n JSON.stringify(result),\n );\n if (result?.persistenceMode) {\n setPersistenceMode(result.persistenceMode as 'backend' | 'frontend');\n }\n if (result?.state === 'CONNECTED' || result?.connected) {\n setStatus('connected');\n connectedRef.current = true;\n } else if (result?.state === 'ERROR') {\n setStatus('error');\n setError(result.lastError ?? 'Failed to connect to CDeCLI agent');\n connectedRef.current = false;\n } else {\n // Assume connecting — the subscription will push the final state\n setStatus('connected');\n connectedRef.current = true;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[useCdecliAutoConnect] connect error:', msg);\n setStatus('error');\n setError(msg);\n connectedRef.current = false;\n } finally {\n connectingRef.current = false;\n }\n };\n\n doConnect();\n }, [\n isSelected,\n obtainSystemToken,\n connectMutation,\n accountId,\n prerequisitesLoading,\n accountUserId,\n channelId,\n systemTokenLoading,\n ]);\n\n // Keep the backend session alive across page refreshes and SPA navigations.\n // The backend CdecliServeProvider.connect() will reuse the existing session\n // if the endpoint hasn't changed, preserving conversation context.\n // Only disconnect when the user explicitly switches to a different gateway.\n\n /** Reconnect the CDeCLI session with a specific skill pre-activated. */\n const connectWithSkill = useCallback(\n async (skill: string, model?: string, systemPrompt?: string, skillId?: string) => {\n console.log(\n '[useCdecliAutoConnect] connectWithSkill called with skill=%s skillId=%s model=%s systemPrompt=%d chars',\n skill,\n skillId ?? '',\n model,\n systemPrompt?.length ?? 0,\n );\n setStatus('connecting');\n setError(null);\n\n try {\n const endpoint = config.CDECLI_AGENT_ENDPOINT || 'https://cdecli-agent.cdebase.dev';\n const token = await obtainSystemToken();\n\n console.log('[useCdecliAutoConnect] connectWithSkill sending mutation with skill=%s', skill);\n const { data } = await connectMutation({\n variables: {\n input: {\n channelType: CHANNEL_TYPE,\n accountId,\n options: {\n endpoint: endpoint.trim(),\n ...(token ? { token } : {}),\n skill,\n ...(skillId ? { skillId } : {}),\n ...(model ? { model } : {}),\n ...(systemPrompt ? { systemPrompt } : {}),\n ...(channelId ? { chatId: channelId } : {}),\n },\n },\n },\n });\n\n console.log('[useCdecliAutoConnect] connectWithSkill result:', JSON.stringify(data?.gatewayConnect));\n const result = data?.gatewayConnect as\n | ((typeof data)['gatewayConnect'] & { persistenceMode?: string })\n | undefined;\n if (result?.persistenceMode) {\n setPersistenceMode(result.persistenceMode as 'backend' | 'frontend');\n }\n if (result?.state === 'CONNECTED' || result?.connected) {\n setStatus('connected');\n connectedRef.current = true;\n setActiveSkill(skill);\n } else {\n setStatus('connected');\n connectedRef.current = true;\n setActiveSkill(skill);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[useCdecliAutoConnect] connectWithSkill error:', msg);\n setStatus('error');\n setError(msg);\n }\n },\n [connectMutation, obtainSystemToken, accountId, channelId],\n );\n\n return {\n channelConnected: status === 'connected',\n status,\n error,\n persistenceMode,\n activeSkill,\n accountId,\n connectWithSkill,\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useCdecliAutoConnect.js","sources":["../../src/hooks/useCdecliAutoConnect.ts"],"sourcesContent":["/**\n * Auto-connects the cdecli-serve messenger-gateway channel when the CDeCLI\n * gateway is selected from the dropdown. Obtains/creates a system token and\n * calls the `gatewayConnect` mutation so the backend provider has a live\n * session with the cdecli-agent endpoint.\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n useGatewayConnectMutation,\n useGatewayDisconnectMutation,\n useGetUserSystemTokenQuery,\n useCreateSecretApiTokenMutation,\n useRevealSecretApiTokenMutation,\n} from 'common/graphql';\nimport { SecretApiTokenAlgorithm } from 'common';\nimport { config } from '../config/env-config';\nimport { usePrerequisiteIds } from './usePrerequisiteIds';\n\n/** Local backend ships RS256 public key but often no private key; HS256 uses dev JWT secret. */\nconst SYSTEM_TOKEN_ALGORITHM = __DEV__ ? SecretApiTokenAlgorithm.Hs256 : SecretApiTokenAlgorithm.Rs256;\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport type CdecliAutoConnectStatus = 'idle' | 'connecting' | 'connected' | 'error';\n\nexport interface UseCdecliAutoConnectResult {\n /** Whether the backend cdecli-serve channel is connected. */\n channelConnected: boolean;\n /** Current connection status. */\n status: CdecliAutoConnectStatus;\n /** Error message if status is 'error'. */\n error: string | null;\n /** Persistence mode reported by the backend provider ('backend' | 'frontend'). */\n persistenceMode: 'backend' | 'frontend' | undefined;\n /** Active skill name on the current CDeCLI session (null if none). */\n activeSkill: string | null;\n /** Per-user accountId used for this gateway connection. */\n accountId: string;\n /** Reconnect the CDeCLI session with a specific skill pre-activated. */\n connectWithSkill: (skill: string, model?: string, systemPrompt?: string, skillId?: string) => Promise<void>;\n}\n\n// ─── Constants ───────────────────────────────────────────────────\n\nconst CHANNEL_TYPE = 'cdecli-serve';\n\n// ─── Hook ────────────────────────────────────────────────────────\n\n/**\n * When `isSelected` is true, auto-connects the cdecli-serve channel on the\n * messenger-gateway backend using the hosted endpoint and a system token.\n */\nexport function useCdecliAutoConnect(isSelected: boolean, channelId?: string): UseCdecliAutoConnectResult {\n const [status, setStatus] = useState<CdecliAutoConnectStatus>('idle');\n const [error, setError] = useState<string | null>(null);\n const [persistenceMode, setPersistenceMode] = useState<'backend' | 'frontend' | undefined>(undefined);\n const [activeSkill, setActiveSkill] = useState<string | null>(null);\n const connectingRef = useRef(false);\n const connectedRef = useRef(false);\n\n const { orgName, projectId, tagId, accountUserId, loading: prerequisitesLoading } = usePrerequisiteIds();\n\n // Per-user accountId so each user gets their own cdecli-agent session.\n // Wait for the profile query to load before connecting — otherwise all\n // users fall back to 'default' and share a single cdecli-agent session.\n const accountId = accountUserId ?? 'default';\n\n // Track the previous channelId so we can reconnect when the user opens a new chat.\n const prevChannelIdRef = useRef<string | undefined>(channelId);\n\n const [connectMutation] = useGatewayConnectMutation();\n const [disconnectMutation] = useGatewayDisconnectMutation();\n const [createTokenMutation] = useCreateSecretApiTokenMutation();\n const [revealTokenMutation] = useRevealSecretApiTokenMutation();\n const {\n data: systemTokenData,\n loading: systemTokenLoading,\n refetch: refetchSystemToken,\n } = useGetUserSystemTokenQuery({\n skip: !isSelected,\n fetchPolicy: 'network-only',\n });\n\n /** True when the backend rejects createSecretApiToken because the user already has one. */\n const isAlreadyHasSystemTokenError = (err: unknown): boolean => {\n const message = err instanceof Error ? err.message : String(err ?? '');\n return /already have a system token/i.test(message);\n };\n\n const obtainSystemToken = useCallback(async (): Promise<string | null> => {\n try {\n let tokenId: string | null = systemTokenData?.getUserSystemToken?.id ?? null;\n\n /**\n * Race recovery: if the GetUserSystemToken query hasn't returned yet (or returned\n * stale data) we'll fall through to createSecretApiToken. The backend rejects that\n * with \"you already have a system token\" — catch it, refetch, and use the existing\n * token id instead of falling back to an unauthenticated connection.\n */\n if (!tokenId) {\n if (!orgName || !projectId) {\n const refetchResult = await refetchSystemToken().catch(() => null);\n tokenId = refetchResult?.data?.getUserSystemToken?.id ?? null;\n if (!tokenId) return null;\n } else {\n try {\n const { data: createData } = await createTokenMutation({\n variables: {\n input: {\n name: 'System Token',\n expiryInSeconds: 31536000,\n isSystemToken: true,\n environmentTagId: tagId,\n orgName: orgName || 'default',\n projectId: projectId || 'default',\n algorithm: SYSTEM_TOKEN_ALGORITHM,\n },\n },\n });\n tokenId = createData?.createSecretApiToken?.id ?? null;\n await refetchSystemToken();\n } catch (createErr) {\n if (!isAlreadyHasSystemTokenError(createErr)) throw createErr;\n const refetchResult = await refetchSystemToken().catch(() => null);\n tokenId = refetchResult?.data?.getUserSystemToken?.id ?? null;\n if (!tokenId) {\n console.warn(\n '[useCdecliAutoConnect] System token already exists but refetch returned no id; retrying',\n );\n return null;\n }\n }\n }\n }\n\n if (!tokenId) return null;\n\n const { data: revealData } = await revealTokenMutation({\n variables: { id: tokenId },\n });\n return (revealData?.revealSecretApiToken as string) || null;\n } catch (err) {\n console.error('[useCdecliAutoConnect] obtainSystemToken error:', err);\n return null;\n }\n }, [systemTokenData, createTokenMutation, revealTokenMutation, refetchSystemToken, orgName, projectId, tagId]);\n\n // Auto-connect when CDeCLI gateway is selected\n useEffect(() => {\n if (!isSelected) {\n // Reset when deselected, but don't disconnect — keep the session alive\n // so switching back is instant.\n connectingRef.current = false;\n return;\n }\n\n // Wait until the user profile has loaded so accountId is the real\n // user id instead of the 'default' fallback.\n if (prerequisitesLoading || !accountUserId) return;\n\n /**\n * Wait for the GetUserSystemToken query to resolve before we connect — otherwise we\n * race the network and try to create a duplicate token, which the backend rejects with\n * \"you already have a system token\" and we fall back to an unauthenticated session.\n */\n if (systemTokenLoading) return;\n\n /**\n * If we're already connected and just switching channels, the gateway needs a reconnect\n * (the session is bound server-side to chatId), but the user already had a working\n * connection. Skip flipping the visible status to 'connecting' so the toolbar pill\n * doesn't briefly show \"Checking...\" every time someone taps a history row.\n */\n let isSilentReconnect = false;\n if (connectedRef.current || connectingRef.current) {\n if (channelId && prevChannelIdRef.current !== channelId) {\n isSilentReconnect = connectedRef.current;\n prevChannelIdRef.current = channelId;\n connectedRef.current = false;\n } else {\n return;\n }\n }\n\n const doConnect = async () => {\n connectingRef.current = true;\n if (!isSilentReconnect) setStatus('connecting');\n setError(null);\n\n try {\n const endpoint = config.CDECLI_AGENT_ENDPOINT || 'https://cdecli-agent.cdebase.dev';\n const token = await obtainSystemToken();\n if (!token) {\n console.warn('[useCdecliAutoConnect] No system token, connecting without auth');\n }\n\n const { data } = await connectMutation({\n variables: {\n input: {\n channelType: CHANNEL_TYPE,\n accountId,\n options: {\n endpoint: endpoint.trim(),\n ...(token ? { token } : {}),\n ...(channelId ? { chatId: channelId } : {}),\n },\n },\n },\n });\n\n const result = data?.gatewayConnect as\n | ((typeof data)['gatewayConnect'] & { persistenceMode?: string })\n | undefined;\n console.log(\n '[useCdecliAutoConnect] connectMutation result (account=%s):',\n accountId,\n JSON.stringify(result),\n );\n if (result?.persistenceMode) {\n setPersistenceMode(result.persistenceMode as 'backend' | 'frontend');\n }\n if (result?.state === 'CONNECTED' || result?.connected) {\n setStatus('connected');\n connectedRef.current = true;\n } else if (result?.state === 'ERROR') {\n setStatus('error');\n setError(result.lastError ?? 'Failed to connect to CDeCLI agent');\n connectedRef.current = false;\n } else {\n // Assume connecting — the subscription will push the final state\n setStatus('connected');\n connectedRef.current = true;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[useCdecliAutoConnect] connect error:', msg);\n setStatus('error');\n setError(msg);\n connectedRef.current = false;\n } finally {\n connectingRef.current = false;\n }\n };\n\n doConnect();\n }, [\n isSelected,\n obtainSystemToken,\n connectMutation,\n accountId,\n prerequisitesLoading,\n accountUserId,\n channelId,\n systemTokenLoading,\n ]);\n\n // Keep the backend session alive across page refreshes and SPA navigations.\n // The backend CdecliServeProvider.connect() will reuse the existing session\n // if the endpoint hasn't changed, preserving conversation context.\n // Only disconnect when the user explicitly switches to a different gateway.\n\n /** Reconnect the CDeCLI session with a specific skill pre-activated. */\n const connectWithSkill = useCallback(\n async (skill: string, model?: string, systemPrompt?: string, skillId?: string) => {\n console.log(\n '[useCdecliAutoConnect] connectWithSkill called with skill=%s skillId=%s model=%s systemPrompt=%d chars',\n skill,\n skillId ?? '',\n model,\n systemPrompt?.length ?? 0,\n );\n setStatus('connecting');\n setError(null);\n\n try {\n const endpoint = config.CDECLI_AGENT_ENDPOINT || 'https://cdecli-agent.cdebase.dev';\n const token = await obtainSystemToken();\n\n console.log('[useCdecliAutoConnect] connectWithSkill sending mutation with skill=%s', skill);\n const { data } = await connectMutation({\n variables: {\n input: {\n channelType: CHANNEL_TYPE,\n accountId,\n options: {\n endpoint: endpoint.trim(),\n ...(token ? { token } : {}),\n skill,\n ...(skillId ? { skillId } : {}),\n ...(model ? { model } : {}),\n ...(systemPrompt ? { systemPrompt } : {}),\n ...(channelId ? { chatId: channelId } : {}),\n },\n },\n },\n });\n\n console.log('[useCdecliAutoConnect] connectWithSkill result:', JSON.stringify(data?.gatewayConnect));\n const result = data?.gatewayConnect as\n | ((typeof data)['gatewayConnect'] & { persistenceMode?: string })\n | undefined;\n if (result?.persistenceMode) {\n setPersistenceMode(result.persistenceMode as 'backend' | 'frontend');\n }\n if (result?.state === 'CONNECTED' || result?.connected) {\n setStatus('connected');\n connectedRef.current = true;\n setActiveSkill(skill);\n } else {\n setStatus('connected');\n connectedRef.current = true;\n setActiveSkill(skill);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[useCdecliAutoConnect] connectWithSkill error:', msg);\n setStatus('error');\n setError(msg);\n }\n },\n [connectMutation, obtainSystemToken, accountId, channelId],\n );\n\n return {\n channelConnected: status === 'connected',\n status,\n error,\n persistenceMode,\n activeSkill,\n accountId,\n connectWithSkill,\n };\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import {useRef,useCallback,useEffect}from'react';import {useGatewaySendMessageMutation,useMessengerStreamDeltaSubscription,useGatewayInboundMessageByChannelSubscription}from'common/graphql';import {config}from'../config/env-config.js';var __defProp = Object.defineProperty;
1
+ import {useRef,useCallback,useEffect}from'react';import {useGatewaySendMessageMutation,useMessengerStreamDeltaSubscription,useGatewayInboundMessageByChannelSubscription}from'common/graphql';var __defProp = Object.defineProperty;
2
2
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
3
  var __hasOwnProp = Object.prototype.hasOwnProperty;
4
4
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -14,23 +14,20 @@ var __spreadValues = (a, b) => {
14
14
  }
15
15
  return a;
16
16
  };
17
- const CDECLI_RESPONSE_WAIT_MS = config.CDECLI_CHAT_RESPONSE_TIMEOUT_SEC * 1e3;
18
17
  function stripModelCostHeader(content) {
19
18
  const normalized = content.replace(/\r\n/g, "\n");
20
19
  return normalized.replace(/^\s*(?:[^\w\n]+\s*)?[a-z0-9][a-z0-9._-]*\s*\(\s*\$[\d.]+\s*\/\s*MTok\s+in\s*\)\s*\n+/i, "");
21
20
  }
22
- function useCdecliChannel(enabled, isConnected, accountId, channelId, callbacks, model, skill) {
21
+ const CDECLI_RESPONSE_TIMEOUT_MS = 12e4;
22
+ function useCdecliChannel(isConnected, accountId, channelId, callbacks, model, skill) {
23
23
  const [sendMutation] = useGatewaySendMessageMutation();
24
24
  const cbRef = useRef(callbacks);
25
25
  cbRef.current = callbacks;
26
26
  const pendingRef = useRef(null);
27
- const sendNonceSeqRef = useRef(0);
28
- const lastInboundNonceHandledRef = useRef(0);
29
27
  const hasReceivedDeltasRef = useRef(false);
30
28
  const reconnectedStreamRef = useRef(false);
31
29
  const streamCompletedRef = useRef(false);
32
30
  const accumulatedLenRef = useRef(0);
33
- const lastAccumulatedFullTextRef = useRef("");
34
31
  const timeoutRef = useRef(null);
35
32
  const streamSkip = !channelId;
36
33
  const streamChannelId = channelId || accountId;
@@ -38,47 +35,14 @@ function useCdecliChannel(enabled, isConnected, accountId, channelId, callbacks,
38
35
  variables: {
39
36
  channelId: streamChannelId
40
37
  },
41
- skip: !enabled || streamSkip,
38
+ skip: streamSkip,
42
39
  onData: ({
43
40
  data
44
41
  }) => {
45
- var _a, _b, _c;
42
+ var _a, _b;
46
43
  const delta = (_a = data == null ? void 0 : data.data) == null ? void 0 : _a.messengerStreamDelta;
47
44
  if (!delta) return;
48
- if (delta.isFinal) {
49
- if (streamCompletedRef.current) return;
50
- const fromDelta = stripModelCostHeader((_b = delta.text) != null ? _b : "");
51
- const fullText2 = fromDelta.trim().length > 0 ? fromDelta : lastAccumulatedFullTextRef.current;
52
- if (!fullText2.trim()) {
53
- if (pendingRef.current) {
54
- streamCompletedRef.current = true;
55
- cbRef.current.onError("CDeCLI returned an empty response.");
56
- pendingRef.current = null;
57
- hasReceivedDeltasRef.current = false;
58
- lastAccumulatedFullTextRef.current = "";
59
- if (timeoutRef.current) {
60
- clearTimeout(timeoutRef.current);
61
- timeoutRef.current = null;
62
- }
63
- }
64
- return;
65
- }
66
- streamCompletedRef.current = true;
67
- if (!hasReceivedDeltasRef.current) {
68
- cbRef.current.onChunk(fullText2);
69
- }
70
- cbRef.current.onComplete(fullText2);
71
- pendingRef.current = null;
72
- hasReceivedDeltasRef.current = false;
73
- reconnectedStreamRef.current = false;
74
- accumulatedLenRef.current = 0;
75
- lastAccumulatedFullTextRef.current = "";
76
- if (timeoutRef.current) {
77
- clearTimeout(timeoutRef.current);
78
- timeoutRef.current = null;
79
- }
80
- return;
81
- }
45
+ if (delta.isFinal) return;
82
46
  if (streamCompletedRef.current) return;
83
47
  if (!pendingRef.current) {
84
48
  if (!reconnectedStreamRef.current) {
@@ -87,8 +51,7 @@ function useCdecliChannel(enabled, isConnected, accountId, channelId, callbacks,
87
51
  }
88
52
  }
89
53
  hasReceivedDeltasRef.current = true;
90
- const fullText = stripModelCostHeader((_c = delta.text) != null ? _c : "");
91
- lastAccumulatedFullTextRef.current = fullText;
54
+ const fullText = stripModelCostHeader((_b = delta.text) != null ? _b : "");
92
55
  const newChunk = fullText.substring(accumulatedLenRef.current);
93
56
  accumulatedLenRef.current = fullText.length;
94
57
  if (newChunk) {
@@ -103,46 +66,23 @@ function useCdecliChannel(enabled, isConnected, accountId, channelId, callbacks,
103
66
  variables: {
104
67
  channelId: streamChannelId
105
68
  },
106
- skip: !enabled || !isConnected || streamSkip,
69
+ skip: !isConnected || streamSkip,
107
70
  onData: ({
108
71
  data
109
72
  }) => {
110
- var _a, _b;
73
+ var _a;
111
74
  const msg = (_a = data == null ? void 0 : data.data) == null ? void 0 : _a.gatewayInboundMessageByChannel;
112
- if (!msg) return;
113
- const pending = pendingRef.current;
114
- const nonce = pending == null ? void 0 : pending.nonce;
115
- if (nonce == null) return;
116
- if (lastInboundNonceHandledRef.current === nonce) {
117
- return;
118
- }
119
- lastInboundNonceHandledRef.current = nonce;
120
- const sanitizedText = stripModelCostHeader((_b = msg.text) != null ? _b : "");
121
- const effectiveText = sanitizedText.trim().length > 0 ? sanitizedText : lastAccumulatedFullTextRef.current;
122
- if (!effectiveText.trim()) {
123
- if (pendingRef.current) {
124
- streamCompletedRef.current = true;
125
- cbRef.current.onError("CDeCLI returned an empty response.");
126
- pendingRef.current = null;
127
- hasReceivedDeltasRef.current = false;
128
- lastAccumulatedFullTextRef.current = "";
129
- if (timeoutRef.current) {
130
- clearTimeout(timeoutRef.current);
131
- timeoutRef.current = null;
132
- }
133
- }
134
- return;
135
- }
75
+ if (!(msg == null ? void 0 : msg.text)) return;
76
+ const sanitizedText = stripModelCostHeader(msg.text);
136
77
  if (!hasReceivedDeltasRef.current) {
137
- cbRef.current.onChunk(effectiveText);
78
+ cbRef.current.onChunk(sanitizedText);
138
79
  }
139
- cbRef.current.onComplete(effectiveText);
80
+ cbRef.current.onComplete(sanitizedText);
140
81
  pendingRef.current = null;
141
82
  hasReceivedDeltasRef.current = false;
142
83
  reconnectedStreamRef.current = false;
143
84
  streamCompletedRef.current = true;
144
85
  accumulatedLenRef.current = 0;
145
- lastAccumulatedFullTextRef.current = "";
146
86
  if (timeoutRef.current) {
147
87
  clearTimeout(timeoutRef.current);
148
88
  timeoutRef.current = null;
@@ -155,18 +95,15 @@ function useCdecliChannel(enabled, isConnected, accountId, channelId, callbacks,
155
95
  });
156
96
  const sendMessage = useCallback(async (text, chatId = "messenger") => {
157
97
  var _a;
158
- if (!enabled || !isConnected) return false;
159
- sendNonceSeqRef.current += 1;
98
+ if (!isConnected) return false;
160
99
  pendingRef.current = {
161
100
  text,
162
- sentAt: Date.now(),
163
- nonce: sendNonceSeqRef.current
101
+ sentAt: Date.now()
164
102
  };
165
103
  hasReceivedDeltasRef.current = false;
166
104
  reconnectedStreamRef.current = false;
167
105
  streamCompletedRef.current = false;
168
106
  accumulatedLenRef.current = 0;
169
- lastAccumulatedFullTextRef.current = "";
170
107
  try {
171
108
  const result = await sendMutation({
172
109
  variables: {
@@ -196,10 +133,9 @@ function useCdecliChannel(enabled, isConnected, accountId, channelId, callbacks,
196
133
  pendingRef.current = null;
197
134
  hasReceivedDeltasRef.current = false;
198
135
  timeoutRef.current = null;
199
- const sec = Math.round(CDECLI_RESPONSE_WAIT_MS / 1e3);
200
- cbRef.current.onError(`CDeCLI agent did not respond within ${sec}s. Trying the backup chat provider\u2026`);
136
+ cbRef.current.onError("CDeCLI agent did not respond within 120 seconds. The query may still be processing.");
201
137
  }
202
- }, CDECLI_RESPONSE_WAIT_MS);
138
+ }, CDECLI_RESPONSE_TIMEOUT_MS);
203
139
  return true;
204
140
  } catch (err) {
205
141
  const msg = err instanceof Error ? err.message : String(err);
@@ -207,14 +143,13 @@ function useCdecliChannel(enabled, isConnected, accountId, channelId, callbacks,
207
143
  pendingRef.current = null;
208
144
  return false;
209
145
  }
210
- }, [enabled, isConnected, accountId, channelId, sendMutation, model, skill]);
146
+ }, [isConnected, accountId, channelId, sendMutation, model, skill]);
211
147
  useEffect(() => () => {
212
148
  pendingRef.current = null;
213
149
  hasReceivedDeltasRef.current = false;
214
150
  reconnectedStreamRef.current = false;
215
151
  streamCompletedRef.current = false;
216
152
  accumulatedLenRef.current = 0;
217
- lastAccumulatedFullTextRef.current = "";
218
153
  if (timeoutRef.current) {
219
154
  clearTimeout(timeoutRef.current);
220
155
  timeoutRef.current = null;