@adminide-stack/yantra-mobile 12.0.30-alpha.7 → 12.0.30-alpha.9

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.
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sources":["../../src/config/constants.ts"],"sourcesContent":["/** Minimal gateway types for mobile — aligned with account/browser. */\r\n\r\nexport type GatewayId = string;\r\n\r\nexport interface GatewayEntry {\r\n name: string;\r\n description: string;\r\n channelType: string;\r\n extensionId: string;\r\n icon: string;\r\n isBuiltin: boolean;\r\n priority: number;\r\n endpoint?: string;\r\n persistenceMode?: 'backend' | 'frontend';\r\n}\r\n\r\n/** CDeCLI / cdecli-serve (hosted agent). */\r\nconst CDECLI_GATEWAY: GatewayEntry = {\r\n name: 'CDeCLI',\r\n description: 'CDeCLI agent and skills',\r\n channelType: 'cdecli-serve',\r\n extensionId: '',\r\n icon: 'terminal',\r\n isBuiltin: true,\r\n priority: 90,\r\n /** Server persists chat posts via gateway — client must not call `saveMessages`. */\r\n persistenceMode: 'backend',\r\n};\r\n\r\n/** Built-in gateways when UiLayout settings are not loaded (e.g. React Native). */\r\nexport const BUILTIN_MOBILE_GATEWAYS: Record<string, GatewayEntry> = {\r\n cdecli: CDECLI_GATEWAY,\r\n};\r\n\r\nexport const STORAGE_KEYS = {\r\n GATEWAY_SELECTION: 'yantra_gateway_selection',\r\n} as const;\r\n\r\n/** Maximum concurrent agent tasks (same cap as browser). */\r\nexport const MAX_CONCURRENT_TASKS = 3;\r\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"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"env-config.js","sources":["../../src/config/env-config.ts"],"sourcesContent":["import { str, cleanEnv, bool, num } from 'envalid';\r\n\r\nconst env = (process as { APP_ENV?: NodeJS.ProcessEnv; env?: NodeJS.ProcessEnv }).APP_ENV || process.env;\r\n\r\nconst baseConfig = cleanEnv(env, {\r\n GRAPHQL_URL: str({ default: 'http://localhost:8080/graphql' }),\r\n CLIENT_URL: str({ default: 'http://localhost:3000' }),\r\n APP_NAME: str({ default: 'Yantra' }),\r\n GROQ_API_KEY: str({ default: '' }),\r\n CDECLI_AGENT_ENDPOINT: str({ default: 'https://cdecli-agent.cdebase.dev' }),\r\n CDECLI_AGENT_AUTH_TOKEN: str({ default: '' }),\r\n /** Seconds to wait for CDeCLI stream completion before timing out (then fallback chat API runs). Min 15, max 600. */\r\n /** Aligned with web `useCdecliChannel` (120s). Mobile hook uses the same constant inline. */\r\n CDECLI_CHAT_RESPONSE_TIMEOUT_SEC: num({ default: 120 }),\r\n BACKEND_URL: str({ default: '' }),\r\n USE_PROXY_STAGING_GATEWAY: bool({ default: false }),\r\n PROXY_STAGING_GATEWAY_URL: str({ default: '' }),\r\n});\r\n\r\nfunction resolveAgentGatewayGraphqlUrl(): string {\r\n if (baseConfig.USE_PROXY_STAGING_GATEWAY && baseConfig.PROXY_STAGING_GATEWAY_URL) {\r\n return baseConfig.PROXY_STAGING_GATEWAY_URL;\r\n }\r\n if (baseConfig.GRAPHQL_URL) return baseConfig.GRAPHQL_URL;\r\n if (baseConfig.BACKEND_URL) return `${baseConfig.BACKEND_URL.replace(/\\/$/, '')}/graphql`;\r\n return '/graphql';\r\n}\r\n\r\nfunction resolveBaseWsUrl(graphqlUrl: string): string {\r\n const base = graphqlUrl.replace(/\\/graphql$/, '');\r\n return base.replace(/^http/, 'ws');\r\n}\r\n\r\nfunction resolveAgentGatewayWsUrl(graphqlUrl: string): string {\r\n return `${resolveBaseWsUrl(graphqlUrl)}/ws/openclaw-chat`;\r\n}\r\n\r\nconst agentGatewayGraphqlUrl = resolveAgentGatewayGraphqlUrl();\r\n\r\nconst clampedCdecliTimeoutSec = Math.min(600, Math.max(15, Math.round(baseConfig.CDECLI_CHAT_RESPONSE_TIMEOUT_SEC)));\r\n\r\nexport const config = {\r\n ...baseConfig,\r\n CDECLI_CHAT_RESPONSE_TIMEOUT_SEC: clampedCdecliTimeoutSec,\r\n AGENT_GATEWAY_GRAPHQL_URL: agentGatewayGraphqlUrl,\r\n AGENT_GATEWAY_WS_URL: resolveAgentGatewayWsUrl(agentGatewayGraphqlUrl),\r\n};\r\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
+ {"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';\r\nimport { ActivityIndicator, Animated, Easing, Pressable, StyleSheet, View } from 'react-native';\r\nimport { Feather, Ionicons } from '@expo/vector-icons';\r\nimport { AudioModule, RecordingPresets, setAudioModeAsync, useAudioRecorder, useAudioRecorderState } from 'expo-audio';\r\nimport { transcribeAudio } from '../../api/stt';\r\n\r\n/**\r\n * Mirrors the web ceiling in\r\n * `packages-modules/account/browser/src/features/audio-input/components/AudioRecorder.tsx`.\r\n * Three minutes is a soft Whisper ceiling — past that, accuracy and upload\r\n * time both degrade more than the marginal extra context is worth.\r\n */\r\nconst MAX_DURATION_MS = 3 * 60 * 1000;\r\n\r\nexport interface AudioRecorderPanelProps {\r\n isDark: boolean;\r\n /**\r\n * Called once with the final transcribed string after the user taps \"Send\".\r\n * The host screen typically appends this to its composer state and re-focuses\r\n * the text input — mirroring the web `handleTranscriptionComplete` flow.\r\n */\r\n onTranscriptionComplete: (text: string) => void;\r\n /** Called when the user discards the recording (X) or recording cannot start. */\r\n onCancel: () => void;\r\n /** Optional error sink — host can surface a toast/banner. */\r\n onError?: (error: string) => void;\r\n}\r\n\r\n/**\r\n * Inline voice-capture panel that replaces the composer while recording —\r\n * the React Native port of the web `AudioRecorder` component\r\n * (`packages-modules/account/browser/src/features/audio-input/components/AudioRecorder.tsx`).\r\n *\r\n * Flow (parity with web):\r\n * 1. Mount → request mic permission → set audio mode → prepare + start recording.\r\n * 2. While recording, display a live timer and pulsing record dot.\r\n * 3. User taps \"Send\" → stop recorder → upload `recorder.uri` to Groq Whisper\r\n * via `transcribeAudio` → return the transcribed text to the host via\r\n * `onTranscriptionComplete`.\r\n * 4. User taps \"Cancel\" (X) → stop + discard, no upload.\r\n * 5. If recording exceeds `MAX_DURATION_MS`, auto-finalize as if the user\r\n * tapped Send (matches web safety net).\r\n *\r\n * The panel is intentionally NOT rendered inside the `InputToolBar`'s `topContent`\r\n * slot — web fully swaps the toolbar for the recorder. Keeping the toolbar\r\n * visible during recording invites accidental sends with empty input and an\r\n * unfocused recorder state.\r\n */\r\nexport function AudioRecorderPanel({ isDark, onTranscriptionComplete, onCancel, onError }: AudioRecorderPanelProps) {\r\n const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);\r\n const recorderState = useAudioRecorderState(recorder, 200);\r\n const [isTranscribing, setIsTranscribing] = useState(false);\r\n\r\n /**\r\n * Guard against re-entry. `useEffect` runs once on mount but a fast double-\r\n * tap on the mic can produce two panel mounts in dev mode (StrictMode);\r\n * `hasStartedRef` ensures we don't start a second recorder against the\r\n * same shared object.\r\n */\r\n const hasStartedRef = useRef(false);\r\n const isMountedRef = useRef(true);\r\n /**\r\n * Caches the result of the in-flight transcription/cleanup pipeline so the\r\n * MAX_DURATION watcher and the explicit Send button can both call\r\n * `finalize()` without racing each other into a double-upload.\r\n */\r\n const sendInFlightRef = useRef(false);\r\n\r\n /**\r\n * Pulsing dot for the \"REC\" indicator. Pure UI sugar; avoids importing\r\n * reanimated for a 2-keyframe loop.\r\n */\r\n const pulse = useRef(new Animated.Value(1)).current;\r\n useEffect(() => {\r\n const loop = Animated.loop(\r\n Animated.sequence([\r\n Animated.timing(pulse, {\r\n toValue: 0.3,\r\n duration: 700,\r\n easing: Easing.inOut(Easing.ease),\r\n useNativeDriver: true,\r\n }),\r\n Animated.timing(pulse, {\r\n toValue: 1,\r\n duration: 700,\r\n easing: Easing.inOut(Easing.ease),\r\n useNativeDriver: true,\r\n }),\r\n ]),\r\n );\r\n loop.start();\r\n return () => loop.stop();\r\n }, [pulse]);\r\n\r\n const stopAndCleanup = useCallback(async () => {\r\n try {\r\n if (recorderState.isRecording || recorder.isRecording) {\r\n await recorder.stop().catch(() => undefined);\r\n }\r\n } catch {\r\n /* best-effort */\r\n }\r\n try {\r\n /*\r\n * Releasing the recording audio session matters more on iOS — leaving\r\n * `allowsRecording: true` will dim playback for the rest of the app\r\n * lifecycle. We tolerate failures because the AVAudioSession may\r\n * already be torn down by the time we get here.\r\n */\r\n await setAudioModeAsync({ allowsRecording: false, playsInSilentMode: true });\r\n } catch {\r\n /* best-effort */\r\n }\r\n }, [recorder, recorderState.isRecording]);\r\n\r\n /**\r\n * One-shot finalize: stop → upload → emit transcript. Swallowed errors get\r\n * surfaced via `onError` so the host can show a toast. We always clear\r\n * `sendInFlightRef` even on error so a retry path is possible if needed.\r\n */\r\n const finalize = useCallback(async () => {\r\n if (sendInFlightRef.current || isTranscribing) return;\r\n sendInFlightRef.current = true;\r\n if (isMountedRef.current) setIsTranscribing(true);\r\n try {\r\n await stopAndCleanup();\r\n const uri = recorder.uri;\r\n if (!uri) {\r\n throw new Error('No recording produced');\r\n }\r\n const text = await transcribeAudio({ uri, mimeType: 'audio/m4a', filename: 'audio.m4a' });\r\n if (isMountedRef.current) {\r\n onTranscriptionComplete(text);\r\n }\r\n } catch (err) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n onError?.(msg);\r\n onCancel();\r\n } finally {\r\n sendInFlightRef.current = false;\r\n if (isMountedRef.current) setIsTranscribing(false);\r\n }\r\n }, [isTranscribing, recorder, stopAndCleanup, onTranscriptionComplete, onError, onCancel]);\r\n\r\n useEffect(() => {\r\n if (hasStartedRef.current) return;\r\n hasStartedRef.current = true;\r\n let cancelled = false;\r\n const start = async () => {\r\n try {\r\n /*\r\n * Host screen already called `requestMicPermission()` (expo-audio\r\n * docs flow) before mounting this panel — go straight to\r\n * prepare + record.\r\n */\r\n await recorder.prepareToRecordAsync();\r\n if (cancelled || !isMountedRef.current) return;\r\n recorder.record();\r\n } catch (err) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n onError?.(msg);\r\n onCancel();\r\n }\r\n };\r\n void start();\r\n return () => {\r\n cancelled = true;\r\n };\r\n // Mount-only effect: recorder/start refs are stable for the life of the panel.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, []);\r\n\r\n useEffect(() => {\r\n return () => {\r\n isMountedRef.current = false;\r\n /*\r\n * Unmount cleanup: fire-and-forget. We don't await anything here\r\n * because React doesn't allow async destructors and we can't\r\n * guarantee the recorder shared object outlives this tick. The\r\n * stopAndCleanup util is null-safe.\r\n */\r\n void stopAndCleanup();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, []);\r\n\r\n /**\r\n * Max-duration safety net. `RecorderState.durationMillis` already reports\r\n * elapsed time in milliseconds (see `expo-audio` `Audio.types.ts`), which\r\n * lines up directly with our `MAX_DURATION_MS` ceiling (web parity).\r\n */\r\n useEffect(() => {\r\n if (!recorderState.isRecording) return;\r\n const elapsedMs = recorderState.durationMillis ?? 0;\r\n if (elapsedMs >= MAX_DURATION_MS && !sendInFlightRef.current) {\r\n void finalize();\r\n }\r\n }, [recorderState.isRecording, recorderState.durationMillis, finalize]);\r\n\r\n const handleCancelPress = useCallback(async () => {\r\n if (isTranscribing) return;\r\n await stopAndCleanup();\r\n onCancel();\r\n }, [isTranscribing, stopAndCleanup, onCancel]);\r\n\r\n const handleSendPress = useCallback(() => {\r\n void finalize();\r\n }, [finalize]);\r\n\r\n const formatted = useMemo(() => {\r\n const totalSeconds = Math.floor((recorderState.durationMillis ?? 0) / 1000);\r\n const m = Math.floor(totalSeconds / 60);\r\n const s = totalSeconds % 60;\r\n return `${m}:${s.toString().padStart(2, '0')}`;\r\n }, [recorderState.durationMillis]);\r\n\r\n const bg = isDark ? '#0f172a' : '#ffffff';\r\n const border = isDark ? 'rgba(148,163,184,0.18)' : '#e4e4e7';\r\n const fg = isDark ? '#e5e7eb' : '#18181b';\r\n const muted = isDark ? '#94a3b8' : '#71717a';\r\n const cancelBg = isDark ? 'rgba(148,163,184,0.08)' : '#f4f4f5';\r\n\r\n const showSpinner = isTranscribing;\r\n const isRecording = recorderState.isRecording && !showSpinner;\r\n const statusLabel = showSpinner ? 'Transcribing…' : isRecording ? formatted : 'Initializing…';\r\n\r\n return (\r\n <View\r\n accessibilityRole=\"summary\"\r\n accessibilityLabel=\"Voice recorder\"\r\n style={[styles.container, { backgroundColor: bg, borderColor: border }]}\r\n >\r\n <View style={styles.statusRow}>\r\n {showSpinner ? (\r\n <ActivityIndicator size=\"small\" color={fg} />\r\n ) : (\r\n <Animated.View style={[styles.recDot, { opacity: isRecording ? pulse : 0.35 }]} />\r\n )}\r\n <Animated.Text style={[styles.timer, { color: fg }]} accessibilityLiveRegion=\"polite\">\r\n {statusLabel}\r\n </Animated.Text>\r\n {isRecording && <Animated.Text style={[styles.maxLabel, { color: muted }]}>/ 3:00</Animated.Text>}\r\n </View>\r\n <View style={styles.actionsRow}>\r\n <Pressable\r\n onPress={handleCancelPress}\r\n disabled={isTranscribing}\r\n accessibilityLabel=\"Cancel recording\"\r\n accessibilityRole=\"button\"\r\n style={({ pressed }) => [\r\n styles.iconBtn,\r\n {\r\n backgroundColor: cancelBg,\r\n borderColor: border,\r\n opacity: isTranscribing ? 0.4 : pressed ? 0.7 : 1,\r\n },\r\n ]}\r\n >\r\n <Ionicons name=\"close\" size={16} color={fg} />\r\n </Pressable>\r\n <Pressable\r\n onPress={handleSendPress}\r\n disabled={isTranscribing || !isRecording}\r\n accessibilityLabel=\"Stop and transcribe\"\r\n accessibilityRole=\"button\"\r\n style={({ pressed }) => [\r\n styles.sendBtn,\r\n {\r\n opacity: isTranscribing ? 0.7 : !isRecording ? 0.5 : pressed ? 0.85 : 1,\r\n },\r\n ]}\r\n >\r\n {isTranscribing ? (\r\n <ActivityIndicator size=\"small\" color=\"#ffffff\" />\r\n ) : (\r\n <Feather name=\"arrow-up\" size={16} color=\"#ffffff\" />\r\n )}\r\n </Pressable>\r\n </View>\r\n </View>\r\n );\r\n}\r\n\r\nconst styles = StyleSheet.create({\r\n container: {\r\n flexDirection: 'row',\r\n alignItems: 'center',\r\n justifyContent: 'space-between',\r\n borderRadius: 24,\r\n borderWidth: 1,\r\n paddingVertical: 10,\r\n paddingHorizontal: 14,\r\n gap: 12,\r\n shadowColor: '#0f172a',\r\n shadowOpacity: 0.08,\r\n shadowRadius: 12,\r\n shadowOffset: { width: 0, height: 4 },\r\n elevation: 4,\r\n },\r\n statusRow: {\r\n flexDirection: 'row',\r\n alignItems: 'center',\r\n gap: 8,\r\n flexShrink: 1,\r\n },\r\n recDot: {\r\n width: 8,\r\n height: 8,\r\n borderRadius: 4,\r\n backgroundColor: '#ef4444',\r\n },\r\n timer: {\r\n fontSize: 13,\r\n fontWeight: '600',\r\n fontVariant: ['tabular-nums'],\r\n },\r\n maxLabel: {\r\n fontSize: 11,\r\n },\r\n actionsRow: {\r\n flexDirection: 'row',\r\n alignItems: 'center',\r\n gap: 8,\r\n },\r\n iconBtn: {\r\n width: 32,\r\n height: 32,\r\n borderRadius: 16,\r\n borderWidth: 1,\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n },\r\n sendBtn: {\r\n width: 32,\r\n height: 32,\r\n borderRadius: 16,\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n backgroundColor: '#18181b',\r\n },\r\n});\r\n\r\nexport default AudioRecorderPanel;\r\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 { AudioModule, 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 +1 @@
1
- {"version":3,"file":"useAudioPermission.js","sources":["../../../src/features/audio-input/useAudioPermission.ts"],"sourcesContent":["import { AudioModule, setAudioModeAsync } from 'expo-audio';\r\n\r\nexport type MicPermissionResult = {\r\n granted: boolean;\r\n error?: string;\r\n};\r\n\r\n/**\r\n * Official expo-audio v53 recording flow — called when the user taps the mic\r\n * (user-initiated), not at screen mount:\r\n *\r\n * 1. AudioModule.requestRecordingPermissionsAsync()\r\n * 2. setAudioModeAsync({ playsInSilentMode: true, allowsRecording: true })\r\n *\r\n * `expo-audio`'s native `record()` checks permission state set by step 1.\r\n * iOS `setAudioModeAsync` alone can show the system dialog but does NOT update\r\n * expo-audio's internal grant flag → `AudioPermissionsException` on `record()`.\r\n *\r\n * Requires `NSMicrophoneUsageDescription` in the built Info.plist (expo-audio\r\n * plugin + ios.infoPlist). Without it, step 1 can native-crash in older builds.\r\n */\r\nexport async function requestMicPermission(): Promise<MicPermissionResult> {\r\n try {\r\n const status = await AudioModule.requestRecordingPermissionsAsync();\r\n if (!status.granted) {\r\n return {\r\n granted: false,\r\n error: 'Microphone permission is required for voice input.',\r\n };\r\n }\r\n await setAudioModeAsync({\r\n playsInSilentMode: true,\r\n allowsRecording: true,\r\n });\r\n return { granted: true };\r\n } catch (err) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n return { granted: false, error: msg };\r\n }\r\n}\r\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
+ {"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 +1 @@
1
- {"version":3,"file":"useAccountDefaultSettings.js","sources":["../../src/hooks/useAccountDefaultSettings.ts"],"sourcesContent":["import { useSettings } from '@adminide-stack/platform-client';\r\nimport { ContributionSchemaId } from 'common';\r\nimport type { SettingsUriInput } from '../utils/cdecodeUri';\r\n\r\n/**\r\n * Browser reference (`packages-modules/account/browser/src/hooks/usePrerequisiteId.ts`):\r\n *\r\n * ```ts\r\n * useSettingsLoader({\r\n * configKey: 'account.default',\r\n * schemaId: ContributionSchemaId.Configuration,\r\n * resourceUri: undefined,\r\n * includeMarketplace: true,\r\n * });\r\n * ```\r\n *\r\n * On mobile there is no Remix loader, so we use `useSettings` — the same GraphQL\r\n * `pageSettings` query that `useSettingsLoader` runs when `immediate: true`.\r\n */\r\nconst ACCOUNT_DEFAULT_OPTIONS = {\r\n schemaId: ContributionSchemaId.Configuration,\r\n configKey: 'account.default',\r\n includeMarketplace: true,\r\n} as const;\r\n\r\nexport type AccountDefaultSettings = {\r\n organization: string;\r\n project: string;\r\n projectId: string;\r\n};\r\n\r\nfunction readStringField(value: unknown): string {\r\n return typeof value === 'string' ? value.trim() : '';\r\n}\r\n\r\nfunction readProjectField(value: { project?: unknown; projectId?: unknown } | undefined): string {\r\n if (!value) return '';\r\n if (typeof value.project === 'string') return value.project.trim();\r\n if (typeof value.projectId === 'string') return value.projectId.trim();\r\n return '';\r\n}\r\n\r\n/**\r\n * Loads `account.default` for a resource URI (org context, account bootstrap, etc.).\r\n */\r\nexport function useAccountDefaultSettings(params: { resourceUri?: SettingsUriInput; skip?: boolean }) {\r\n const query = useSettings<{\r\n organization?: string;\r\n project?: string;\r\n projectId?: string;\r\n }>({\r\n resourceUri: params.resourceUri as never,\r\n skip: params.skip ?? !params.resourceUri,\r\n options: ACCOUNT_DEFAULT_OPTIONS,\r\n });\r\n\r\n const { data, loading, error } = query;\r\n const { refetch } = query as typeof query & { refetch?: () => Promise<unknown> };\r\n\r\n return {\r\n organization: readStringField(data?.organization),\r\n project: readProjectField(data),\r\n projectId: readStringField(data?.projectId),\r\n loading,\r\n error,\r\n refetch,\r\n };\r\n}\r\n"],"names":[],"mappings":"oGAmBA,MAAM,uBAA0B,GAAA;AAAA,EAC9B,UAAU,oBAAqB,CAAA,aAAA;AAAA,EAC/B,SAAW,EAAA,iBAAA;AAAA,EACX,kBAAoB,EAAA;AACtB,CAAA;AAMA,SAAS,gBAAgB,KAAwB,EAAA;AAC/C,EAAA,OAAO,OAAO,KAAA,KAAU,QAAW,GAAA,KAAA,CAAM,MAAS,GAAA,EAAA;AACpD;AACA,SAAS,iBAAiB,KAGH,EAAA;AACrB,EAAI,IAAA,CAAC,OAAc,OAAA,EAAA;AACnB,EAAA,IAAI,OAAO,KAAM,CAAA,OAAA,KAAY,UAAiB,OAAA,KAAA,CAAM,QAAQ,IAAK,EAAA;AACjE,EAAA,IAAI,OAAO,KAAM,CAAA,SAAA,KAAc,UAAiB,OAAA,KAAA,CAAM,UAAU,IAAK,EAAA;AACrE,EAAO,OAAA,EAAA;AACT;AAKO,SAAS,0BAA0B,MAGvC,EAAA;AAhDH,EAAA,IAAA,EAAA;AAiDE,EAAA,MAAM,QAAQ,WAIX,CAAA;AAAA,IACD,aAAa,MAAO,CAAA,WAAA;AAAA,IACpB,IAAM,EAAA,CAAA,EAAA,GAAA,MAAA,CAAO,IAAP,KAAA,IAAA,GAAA,EAAA,GAAe,CAAC,MAAO,CAAA,WAAA;AAAA,IAC7B,OAAS,EAAA;AAAA,GACV,CAAA;AACD,EAAM,MAAA;AAAA,IACJ,IAAA;AAAA,IACA,OAAA;AAAA,IACA;AAAA,GACE,GAAA,KAAA;AACJ,EAAM,MAAA;AAAA,IACJ;AAAA,GACE,GAAA,KAAA;AAGJ,EAAO,OAAA;AAAA,IACL,YAAA,EAAc,eAAgB,CAAA,IAAA,IAAA,IAAA,GAAA,MAAA,GAAA,IAAA,CAAM,YAAY,CAAA;AAAA,IAChD,OAAA,EAAS,iBAAiB,IAAI,CAAA;AAAA,IAC9B,SAAA,EAAW,eAAgB,CAAA,IAAA,IAAA,IAAA,GAAA,MAAA,GAAA,IAAA,CAAM,SAAS,CAAA;AAAA,IAC1C,OAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF;AACF"}
1
+ {"version":3,"file":"useAccountDefaultSettings.js","sources":["../../src/hooks/useAccountDefaultSettings.ts"],"sourcesContent":["import { useSettings } from '@adminide-stack/platform-client';\nimport { ContributionSchemaId } from 'common';\nimport type { SettingsUriInput } from '../utils/cdecodeUri';\n\n/**\n * Browser reference (`packages-modules/account/browser/src/hooks/usePrerequisiteId.ts`):\n *\n * ```ts\n * useSettingsLoader({\n * configKey: 'account.default',\n * schemaId: ContributionSchemaId.Configuration,\n * resourceUri: undefined,\n * includeMarketplace: true,\n * });\n * ```\n *\n * On mobile there is no Remix loader, so we use `useSettings` — the same GraphQL\n * `pageSettings` query that `useSettingsLoader` runs when `immediate: true`.\n */\nconst ACCOUNT_DEFAULT_OPTIONS = {\n schemaId: ContributionSchemaId.Configuration,\n configKey: 'account.default',\n includeMarketplace: true,\n} as const;\n\nexport type AccountDefaultSettings = {\n organization: string;\n project: string;\n projectId: string;\n};\n\nfunction readStringField(value: unknown): string {\n return typeof value === 'string' ? value.trim() : '';\n}\n\nfunction readProjectField(value: { project?: unknown; projectId?: unknown } | undefined): string {\n if (!value) return '';\n if (typeof value.project === 'string') return value.project.trim();\n if (typeof value.projectId === 'string') return value.projectId.trim();\n return '';\n}\n\n/**\n * Loads `account.default` for a resource URI (org context, account bootstrap, etc.).\n */\nexport function useAccountDefaultSettings(params: { resourceUri?: SettingsUriInput; skip?: boolean }) {\n const query = useSettings<{\n organization?: string;\n project?: string;\n projectId?: string;\n }>({\n resourceUri: params.resourceUri as never,\n skip: params.skip ?? !params.resourceUri,\n options: ACCOUNT_DEFAULT_OPTIONS,\n });\n\n const { data, loading, error } = query;\n const { refetch } = query as typeof query & { refetch?: () => Promise<unknown> };\n\n return {\n organization: readStringField(data?.organization),\n project: readProjectField(data),\n projectId: readStringField(data?.projectId),\n loading,\n error,\n refetch,\n };\n}\n"],"names":[],"mappings":"oGAmBA,MAAM,uBAA0B,GAAA;AAAA,EAC9B,UAAU,oBAAqB,CAAA,aAAA;AAAA,EAC/B,SAAW,EAAA,iBAAA;AAAA,EACX,kBAAoB,EAAA;AACtB,CAAA;AAMA,SAAS,gBAAgB,KAAwB,EAAA;AAC/C,EAAA,OAAO,OAAO,KAAA,KAAU,QAAW,GAAA,KAAA,CAAM,MAAS,GAAA,EAAA;AACpD;AACA,SAAS,iBAAiB,KAGH,EAAA;AACrB,EAAI,IAAA,CAAC,OAAc,OAAA,EAAA;AACnB,EAAA,IAAI,OAAO,KAAM,CAAA,OAAA,KAAY,UAAiB,OAAA,KAAA,CAAM,QAAQ,IAAK,EAAA;AACjE,EAAA,IAAI,OAAO,KAAM,CAAA,SAAA,KAAc,UAAiB,OAAA,KAAA,CAAM,UAAU,IAAK,EAAA;AACrE,EAAO,OAAA,EAAA;AACT;AAKO,SAAS,0BAA0B,MAGvC,EAAA;AAhDH,EAAA,IAAA,EAAA;AAiDE,EAAA,MAAM,QAAQ,WAIX,CAAA;AAAA,IACD,aAAa,MAAO,CAAA,WAAA;AAAA,IACpB,IAAM,EAAA,CAAA,EAAA,GAAA,MAAA,CAAO,IAAP,KAAA,IAAA,GAAA,EAAA,GAAe,CAAC,MAAO,CAAA,WAAA;AAAA,IAC7B,OAAS,EAAA;AAAA,GACV,CAAA;AACD,EAAM,MAAA;AAAA,IACJ,IAAA;AAAA,IACA,OAAA;AAAA,IACA;AAAA,GACE,GAAA,KAAA;AACJ,EAAM,MAAA;AAAA,IACJ;AAAA,GACE,GAAA,KAAA;AAGJ,EAAO,OAAA;AAAA,IACL,YAAA,EAAc,eAAgB,CAAA,IAAA,IAAA,IAAA,GAAA,MAAA,GAAA,IAAA,CAAM,YAAY,CAAA;AAAA,IAChD,OAAA,EAAS,iBAAiB,IAAI,CAAA;AAAA,IAC9B,SAAA,EAAW,eAAgB,CAAA,IAAA,IAAA,IAAA,GAAA,MAAA,GAAA,IAAA,CAAM,SAAS,CAAA;AAAA,IAC1C,OAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useCdecliAutoConnect.js","sources":["../../src/hooks/useCdecliAutoConnect.ts"],"sourcesContent":["/**\r\n * Auto-connects the cdecli-serve messenger-gateway channel when the CDeCLI\r\n * gateway is selected from the dropdown. Obtains/creates a system token and\r\n * calls the `gatewayConnect` mutation so the backend provider has a live\r\n * session with the cdecli-agent endpoint.\r\n */\r\n\r\nimport { useCallback, useEffect, useRef, useState } from 'react';\r\nimport {\r\n useGatewayConnectMutation,\r\n useGatewayDisconnectMutation,\r\n useGetUserSystemTokenQuery,\r\n useCreateSecretApiTokenMutation,\r\n useRevealSecretApiTokenMutation,\r\n} from 'common/graphql';\r\nimport { SecretApiTokenAlgorithm } from 'common';\r\nimport { config } from '../config/env-config';\r\nimport { usePrerequisiteIds } from './usePrerequisiteIds';\r\n\r\n/** Local backend ships RS256 public key but often no private key; HS256 uses dev JWT secret. */\r\nconst SYSTEM_TOKEN_ALGORITHM = __DEV__ ? SecretApiTokenAlgorithm.Hs256 : SecretApiTokenAlgorithm.Rs256;\r\n\r\n// ─── Types ───────────────────────────────────────────────────────\r\n\r\nexport type CdecliAutoConnectStatus = 'idle' | 'connecting' | 'connected' | 'error';\r\n\r\nexport interface UseCdecliAutoConnectResult {\r\n /** Whether the backend cdecli-serve channel is connected. */\r\n channelConnected: boolean;\r\n /** Current connection status. */\r\n status: CdecliAutoConnectStatus;\r\n /** Error message if status is 'error'. */\r\n error: string | null;\r\n /** Persistence mode reported by the backend provider ('backend' | 'frontend'). */\r\n persistenceMode: 'backend' | 'frontend' | undefined;\r\n /** Active skill name on the current CDeCLI session (null if none). */\r\n activeSkill: string | null;\r\n /** Per-user accountId used for this gateway connection. */\r\n accountId: string;\r\n /** Reconnect the CDeCLI session with a specific skill pre-activated. */\r\n connectWithSkill: (skill: string, model?: string, systemPrompt?: string, skillId?: string) => Promise<void>;\r\n}\r\n\r\n// ─── Constants ───────────────────────────────────────────────────\r\n\r\nconst CHANNEL_TYPE = 'cdecli-serve';\r\n\r\n// ─── Hook ────────────────────────────────────────────────────────\r\n\r\n/**\r\n * When `isSelected` is true, auto-connects the cdecli-serve channel on the\r\n * messenger-gateway backend using the hosted endpoint and a system token.\r\n */\r\nexport function useCdecliAutoConnect(isSelected: boolean, channelId?: string): UseCdecliAutoConnectResult {\r\n const [status, setStatus] = useState<CdecliAutoConnectStatus>('idle');\r\n const [error, setError] = useState<string | null>(null);\r\n const [persistenceMode, setPersistenceMode] = useState<'backend' | 'frontend' | undefined>(undefined);\r\n const [activeSkill, setActiveSkill] = useState<string | null>(null);\r\n const connectingRef = useRef(false);\r\n const connectedRef = useRef(false);\r\n\r\n const { orgName, projectId, tagId, accountUserId, loading: prerequisitesLoading } = usePrerequisiteIds();\r\n\r\n // Per-user accountId so each user gets their own cdecli-agent session.\r\n // Wait for the profile query to load before connecting — otherwise all\r\n // users fall back to 'default' and share a single cdecli-agent session.\r\n const accountId = accountUserId ?? 'default';\r\n\r\n // Track the previous channelId so we can reconnect when the user opens a new chat.\r\n const prevChannelIdRef = useRef<string | undefined>(channelId);\r\n\r\n const [connectMutation] = useGatewayConnectMutation();\r\n const [disconnectMutation] = useGatewayDisconnectMutation();\r\n const [createTokenMutation] = useCreateSecretApiTokenMutation();\r\n const [revealTokenMutation] = useRevealSecretApiTokenMutation();\r\n const {\r\n data: systemTokenData,\r\n loading: systemTokenLoading,\r\n refetch: refetchSystemToken,\r\n } = useGetUserSystemTokenQuery({\r\n skip: !isSelected,\r\n fetchPolicy: 'network-only',\r\n });\r\n\r\n /** True when the backend rejects createSecretApiToken because the user already has one. */\r\n const isAlreadyHasSystemTokenError = (err: unknown): boolean => {\r\n const message = err instanceof Error ? err.message : String(err ?? '');\r\n return /already have a system token/i.test(message);\r\n };\r\n\r\n const obtainSystemToken = useCallback(async (): Promise<string | null> => {\r\n try {\r\n let tokenId: string | null = systemTokenData?.getUserSystemToken?.id ?? null;\r\n\r\n /**\r\n * Race recovery: if the GetUserSystemToken query hasn't returned yet (or returned\r\n * stale data) we'll fall through to createSecretApiToken. The backend rejects that\r\n * with \"you already have a system token\" — catch it, refetch, and use the existing\r\n * token id instead of falling back to an unauthenticated connection.\r\n */\r\n if (!tokenId) {\r\n if (!orgName || !projectId) {\r\n const refetchResult = await refetchSystemToken().catch(() => null);\r\n tokenId = refetchResult?.data?.getUserSystemToken?.id ?? null;\r\n if (!tokenId) return null;\r\n } else {\r\n try {\r\n const { data: createData } = await createTokenMutation({\r\n variables: {\r\n input: {\r\n name: 'System Token',\r\n expiryInSeconds: 31536000,\r\n isSystemToken: true,\r\n environmentTagId: tagId,\r\n orgName: orgName || 'default',\r\n projectId: projectId || 'default',\r\n algorithm: SYSTEM_TOKEN_ALGORITHM,\r\n },\r\n },\r\n });\r\n tokenId = createData?.createSecretApiToken?.id ?? null;\r\n await refetchSystemToken();\r\n } catch (createErr) {\r\n if (!isAlreadyHasSystemTokenError(createErr)) throw createErr;\r\n const refetchResult = await refetchSystemToken().catch(() => null);\r\n tokenId = refetchResult?.data?.getUserSystemToken?.id ?? null;\r\n if (!tokenId) {\r\n console.warn(\r\n '[useCdecliAutoConnect] System token already exists but refetch returned no id; retrying',\r\n );\r\n return null;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!tokenId) return null;\r\n\r\n const { data: revealData } = await revealTokenMutation({\r\n variables: { id: tokenId },\r\n });\r\n return (revealData?.revealSecretApiToken as string) || null;\r\n } catch (err) {\r\n console.error('[useCdecliAutoConnect] obtainSystemToken error:', err);\r\n return null;\r\n }\r\n }, [systemTokenData, createTokenMutation, revealTokenMutation, refetchSystemToken, orgName, projectId, tagId]);\r\n\r\n // Auto-connect when CDeCLI gateway is selected\r\n useEffect(() => {\r\n if (!isSelected) {\r\n // Reset when deselected, but don't disconnect — keep the session alive\r\n // so switching back is instant.\r\n connectingRef.current = false;\r\n return;\r\n }\r\n\r\n // Wait until the user profile has loaded so accountId is the real\r\n // user id instead of the 'default' fallback.\r\n if (prerequisitesLoading || !accountUserId) return;\r\n\r\n /**\r\n * Wait for the GetUserSystemToken query to resolve before we connect — otherwise we\r\n * race the network and try to create a duplicate token, which the backend rejects with\r\n * \"you already have a system token\" and we fall back to an unauthenticated session.\r\n */\r\n if (systemTokenLoading) return;\r\n\r\n /**\r\n * If we're already connected and just switching channels, the gateway needs a reconnect\r\n * (the session is bound server-side to chatId), but the user already had a working\r\n * connection. Skip flipping the visible status to 'connecting' so the toolbar pill\r\n * doesn't briefly show \"Checking...\" every time someone taps a history row.\r\n */\r\n let isSilentReconnect = false;\r\n if (connectedRef.current || connectingRef.current) {\r\n if (channelId && prevChannelIdRef.current !== channelId) {\r\n isSilentReconnect = connectedRef.current;\r\n prevChannelIdRef.current = channelId;\r\n connectedRef.current = false;\r\n } else {\r\n return;\r\n }\r\n }\r\n\r\n const doConnect = async () => {\r\n connectingRef.current = true;\r\n if (!isSilentReconnect) setStatus('connecting');\r\n setError(null);\r\n\r\n try {\r\n const endpoint = config.CDECLI_AGENT_ENDPOINT || 'https://cdecli-agent.cdebase.dev';\r\n const token = await obtainSystemToken();\r\n if (!token) {\r\n console.warn('[useCdecliAutoConnect] No system token, connecting without auth');\r\n }\r\n\r\n const { data } = await connectMutation({\r\n variables: {\r\n input: {\r\n channelType: CHANNEL_TYPE,\r\n accountId,\r\n options: {\r\n endpoint: endpoint.trim(),\r\n ...(token ? { token } : {}),\r\n ...(channelId ? { chatId: channelId } : {}),\r\n },\r\n },\r\n },\r\n });\r\n\r\n const result = data?.gatewayConnect as\r\n | ((typeof data)['gatewayConnect'] & { persistenceMode?: string })\r\n | undefined;\r\n console.log(\r\n '[useCdecliAutoConnect] connectMutation result (account=%s):',\r\n accountId,\r\n JSON.stringify(result),\r\n );\r\n if (result?.persistenceMode) {\r\n setPersistenceMode(result.persistenceMode as 'backend' | 'frontend');\r\n }\r\n if (result?.state === 'CONNECTED' || result?.connected) {\r\n setStatus('connected');\r\n connectedRef.current = true;\r\n } else if (result?.state === 'ERROR') {\r\n setStatus('error');\r\n setError(result.lastError ?? 'Failed to connect to CDeCLI agent');\r\n connectedRef.current = false;\r\n } else {\r\n // Assume connecting — the subscription will push the final state\r\n setStatus('connected');\r\n connectedRef.current = true;\r\n }\r\n } catch (err) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n console.error('[useCdecliAutoConnect] connect error:', msg);\r\n setStatus('error');\r\n setError(msg);\r\n connectedRef.current = false;\r\n } finally {\r\n connectingRef.current = false;\r\n }\r\n };\r\n\r\n doConnect();\r\n }, [\r\n isSelected,\r\n obtainSystemToken,\r\n connectMutation,\r\n accountId,\r\n prerequisitesLoading,\r\n accountUserId,\r\n channelId,\r\n systemTokenLoading,\r\n ]);\r\n\r\n // Keep the backend session alive across page refreshes and SPA navigations.\r\n // The backend CdecliServeProvider.connect() will reuse the existing session\r\n // if the endpoint hasn't changed, preserving conversation context.\r\n // Only disconnect when the user explicitly switches to a different gateway.\r\n\r\n /** Reconnect the CDeCLI session with a specific skill pre-activated. */\r\n const connectWithSkill = useCallback(\r\n async (skill: string, model?: string, systemPrompt?: string, skillId?: string) => {\r\n console.log(\r\n '[useCdecliAutoConnect] connectWithSkill called with skill=%s skillId=%s model=%s systemPrompt=%d chars',\r\n skill,\r\n skillId ?? '',\r\n model,\r\n systemPrompt?.length ?? 0,\r\n );\r\n setStatus('connecting');\r\n setError(null);\r\n\r\n try {\r\n const endpoint = config.CDECLI_AGENT_ENDPOINT || 'https://cdecli-agent.cdebase.dev';\r\n const token = await obtainSystemToken();\r\n\r\n console.log('[useCdecliAutoConnect] connectWithSkill sending mutation with skill=%s', skill);\r\n const { data } = await connectMutation({\r\n variables: {\r\n input: {\r\n channelType: CHANNEL_TYPE,\r\n accountId,\r\n options: {\r\n endpoint: endpoint.trim(),\r\n ...(token ? { token } : {}),\r\n skill,\r\n ...(skillId ? { skillId } : {}),\r\n ...(model ? { model } : {}),\r\n ...(systemPrompt ? { systemPrompt } : {}),\r\n ...(channelId ? { chatId: channelId } : {}),\r\n },\r\n },\r\n },\r\n });\r\n\r\n console.log('[useCdecliAutoConnect] connectWithSkill result:', JSON.stringify(data?.gatewayConnect));\r\n const result = data?.gatewayConnect as\r\n | ((typeof data)['gatewayConnect'] & { persistenceMode?: string })\r\n | undefined;\r\n if (result?.persistenceMode) {\r\n setPersistenceMode(result.persistenceMode as 'backend' | 'frontend');\r\n }\r\n if (result?.state === 'CONNECTED' || result?.connected) {\r\n setStatus('connected');\r\n connectedRef.current = true;\r\n setActiveSkill(skill);\r\n } else {\r\n setStatus('connected');\r\n connectedRef.current = true;\r\n setActiveSkill(skill);\r\n }\r\n } catch (err) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n console.error('[useCdecliAutoConnect] connectWithSkill error:', msg);\r\n setStatus('error');\r\n setError(msg);\r\n }\r\n },\r\n [connectMutation, obtainSystemToken, accountId, channelId],\r\n );\r\n\r\n return {\r\n channelConnected: status === 'connected',\r\n status,\r\n error,\r\n persistenceMode,\r\n activeSkill,\r\n accountId,\r\n connectWithSkill,\r\n };\r\n}\r\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 +1 @@
1
- {"version":3,"file":"useCdecliChannel.js","sources":["../../src/hooks/useCdecliChannel.ts"],"sourcesContent":["/**\r\n * useCdecliChannel — wires the cdecli-serve messenger-gateway channel into the mobile chat UI.\r\n *\r\n * Kept in sync with `packages-modules/account/browser/src/hooks/useCdecliChannel.ts`.\r\n * When the CDeCLI channel is connected:\r\n * - `sendMessage(text)` calls `gatewaySendMessage`\r\n * - `MessengerStreamDelta` subscription delivers streaming chunks via `onChunk`\r\n * - `GatewayInboundMessageByChannel` delivers the final reply via `onComplete`\r\n */\r\n\r\nimport { useCallback, useEffect, useRef } from 'react';\r\nimport {\r\n useGatewaySendMessageMutation,\r\n useGatewayInboundMessageByChannelSubscription,\r\n useMessengerStreamDeltaSubscription,\r\n} from 'common/graphql';\r\n\r\nfunction stripModelCostHeader(content: string): string {\r\n const normalized = content.replace(/\\r\\n/g, '\\n');\r\n return normalized.replace(\r\n /^\\s*(?:[^\\w\\n]+\\s*)?[a-z0-9][a-z0-9._-]*\\s*\\(\\s*\\$[\\d.]+\\s*\\/\\s*MTok\\s+in\\s*\\)\\s*\\n+/i,\r\n '',\r\n );\r\n}\r\n\r\nexport interface CdecliChannelCallbacks {\r\n onChunk: (text: string) => void;\r\n onComplete: (text: string) => void;\r\n onError: (error: string) => void;\r\n}\r\n\r\n/** Same 120s window as web — deep CDeCLI queries can exceed 45s. */\r\nconst CDECLI_RESPONSE_TIMEOUT_MS = 300_000;\r\n\r\nexport function useCdecliChannel(\r\n isConnected: boolean,\r\n accountId: string,\r\n channelId: string | undefined,\r\n callbacks: CdecliChannelCallbacks,\r\n model?: string,\r\n skill?: string,\r\n) {\r\n const [sendMutation] = useGatewaySendMessageMutation();\r\n const cbRef = useRef(callbacks);\r\n cbRef.current = callbacks;\r\n\r\n const pendingRef = useRef<{ text: string; sentAt: number } | null>(null);\r\n const hasReceivedDeltasRef = useRef(false);\r\n const reconnectedStreamRef = useRef(false);\r\n const streamCompletedRef = useRef(false);\r\n const accumulatedLenRef = useRef(0);\r\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n\r\n const streamSkip = !channelId;\r\n const streamChannelId = channelId || accountId;\r\n\r\n useMessengerStreamDeltaSubscription({\r\n variables: { channelId: streamChannelId },\r\n skip: streamSkip,\r\n onData: ({ data }) => {\r\n const delta = data?.data?.messengerStreamDelta;\r\n if (!delta) return;\r\n\r\n if (delta.isFinal) return;\r\n\r\n if (streamCompletedRef.current) return;\r\n\r\n if (!pendingRef.current) {\r\n if (!reconnectedStreamRef.current) {\r\n reconnectedStreamRef.current = true;\r\n accumulatedLenRef.current = 0;\r\n }\r\n }\r\n\r\n hasReceivedDeltasRef.current = true;\r\n\r\n const fullText = stripModelCostHeader(delta.text ?? '');\r\n const newChunk = fullText.substring(accumulatedLenRef.current);\r\n accumulatedLenRef.current = fullText.length;\r\n\r\n if (newChunk) {\r\n cbRef.current.onChunk(newChunk);\r\n }\r\n },\r\n onError: (err) => {\r\n console.error('[useCdecliChannel] stream delta subscription error:', err);\r\n },\r\n });\r\n\r\n useGatewayInboundMessageByChannelSubscription({\r\n variables: { channelId: streamChannelId },\r\n skip: !isConnected || streamSkip,\r\n onData: ({ data }) => {\r\n const msg = data?.data?.gatewayInboundMessageByChannel;\r\n if (!msg?.text) return;\r\n const sanitizedText = stripModelCostHeader(msg.text);\r\n\r\n if (!hasReceivedDeltasRef.current) {\r\n cbRef.current.onChunk(sanitizedText);\r\n }\r\n\r\n cbRef.current.onComplete(sanitizedText);\r\n pendingRef.current = null;\r\n hasReceivedDeltasRef.current = false;\r\n reconnectedStreamRef.current = false;\r\n streamCompletedRef.current = true;\r\n accumulatedLenRef.current = 0;\r\n if (timeoutRef.current) {\r\n clearTimeout(timeoutRef.current);\r\n timeoutRef.current = null;\r\n }\r\n },\r\n onError: (err) => {\r\n console.error('[useCdecliChannel] subscription error:', err);\r\n cbRef.current.onError(err.message || 'CDeCLI subscription error');\r\n },\r\n });\r\n\r\n const sendMessage = useCallback(\r\n async (text: string, chatId = 'messenger'): Promise<boolean> => {\r\n if (!isConnected) return false;\r\n\r\n pendingRef.current = { text, sentAt: Date.now() };\r\n hasReceivedDeltasRef.current = false;\r\n reconnectedStreamRef.current = false;\r\n streamCompletedRef.current = false;\r\n accumulatedLenRef.current = 0;\r\n\r\n try {\r\n const result = await sendMutation({\r\n variables: {\r\n input: {\r\n channelType: 'cdecli-serve',\r\n accountId,\r\n chatId,\r\n text,\r\n ...(model || skill\r\n ? { metadata: { ...(model && { model }), ...(skill && { skill }) } }\r\n : {}),\r\n },\r\n },\r\n });\r\n\r\n const payload = result.data?.gatewaySendMessage;\r\n if (!payload?.success) {\r\n const errMsg = payload?.error || 'CDeCLI send failed';\r\n cbRef.current.onError(errMsg);\r\n pendingRef.current = null;\r\n return false;\r\n }\r\n\r\n timeoutRef.current = setTimeout(() => {\r\n if (pendingRef.current) {\r\n pendingRef.current = null;\r\n hasReceivedDeltasRef.current = false;\r\n timeoutRef.current = null;\r\n cbRef.current.onError(\r\n 'CDeCLI agent did not respond within 300 seconds. The query may still be processing.',\r\n );\r\n }\r\n }, CDECLI_RESPONSE_TIMEOUT_MS);\r\n\r\n return true;\r\n } catch (err) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n cbRef.current.onError(msg);\r\n pendingRef.current = null;\r\n return false;\r\n }\r\n },\r\n [isConnected, accountId, channelId, sendMutation, model, skill],\r\n );\r\n\r\n useEffect(\r\n () => () => {\r\n pendingRef.current = null;\r\n hasReceivedDeltasRef.current = false;\r\n reconnectedStreamRef.current = false;\r\n streamCompletedRef.current = false;\r\n accumulatedLenRef.current = 0;\r\n if (timeoutRef.current) {\r\n clearTimeout(timeoutRef.current);\r\n timeoutRef.current = null;\r\n }\r\n },\r\n [],\r\n );\r\n\r\n return { sendMessage };\r\n}\r\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useCdecliChannel.js","sources":["../../src/hooks/useCdecliChannel.ts"],"sourcesContent":["/**\n * useCdecliChannel — wires the cdecli-serve messenger-gateway channel into the mobile chat UI.\n *\n * Kept in sync with `packages-modules/account/browser/src/hooks/useCdecliChannel.ts`.\n * When the CDeCLI channel is connected:\n * - `sendMessage(text)` calls `gatewaySendMessage`\n * - `MessengerStreamDelta` subscription delivers streaming chunks via `onChunk`\n * - `GatewayInboundMessageByChannel` delivers the final reply via `onComplete`\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\nimport {\n useGatewaySendMessageMutation,\n useGatewayInboundMessageByChannelSubscription,\n useMessengerStreamDeltaSubscription,\n} from 'common/graphql';\n\nfunction stripModelCostHeader(content: string): string {\n const normalized = content.replace(/\\r\\n/g, '\\n');\n return normalized.replace(\n /^\\s*(?:[^\\w\\n]+\\s*)?[a-z0-9][a-z0-9._-]*\\s*\\(\\s*\\$[\\d.]+\\s*\\/\\s*MTok\\s+in\\s*\\)\\s*\\n+/i,\n '',\n );\n}\n\nexport interface CdecliChannelCallbacks {\n onChunk: (text: string) => void;\n onComplete: (text: string) => void;\n onError: (error: string) => void;\n}\n\n/** Same 120s window as web — deep CDeCLI queries can exceed 45s. */\nconst CDECLI_RESPONSE_TIMEOUT_MS = 300_000;\n\nexport function useCdecliChannel(\n isConnected: boolean,\n accountId: string,\n channelId: string | undefined,\n callbacks: CdecliChannelCallbacks,\n model?: string,\n skill?: string,\n) {\n const [sendMutation] = useGatewaySendMessageMutation();\n const cbRef = useRef(callbacks);\n cbRef.current = callbacks;\n\n const pendingRef = useRef<{ text: string; sentAt: number } | null>(null);\n const hasReceivedDeltasRef = useRef(false);\n const reconnectedStreamRef = useRef(false);\n const streamCompletedRef = useRef(false);\n const accumulatedLenRef = useRef(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const streamSkip = !channelId;\n const streamChannelId = channelId || accountId;\n\n useMessengerStreamDeltaSubscription({\n variables: { channelId: streamChannelId },\n skip: streamSkip,\n onData: ({ data }) => {\n const delta = data?.data?.messengerStreamDelta;\n if (!delta) return;\n\n if (delta.isFinal) return;\n\n if (streamCompletedRef.current) return;\n\n if (!pendingRef.current) {\n if (!reconnectedStreamRef.current) {\n reconnectedStreamRef.current = true;\n accumulatedLenRef.current = 0;\n }\n }\n\n hasReceivedDeltasRef.current = true;\n\n const fullText = stripModelCostHeader(delta.text ?? '');\n const newChunk = fullText.substring(accumulatedLenRef.current);\n accumulatedLenRef.current = fullText.length;\n\n if (newChunk) {\n cbRef.current.onChunk(newChunk);\n }\n },\n onError: (err) => {\n console.error('[useCdecliChannel] stream delta subscription error:', err);\n },\n });\n\n useGatewayInboundMessageByChannelSubscription({\n variables: { channelId: streamChannelId },\n skip: !isConnected || streamSkip,\n onData: ({ data }) => {\n const msg = data?.data?.gatewayInboundMessageByChannel;\n if (!msg?.text) return;\n const sanitizedText = stripModelCostHeader(msg.text);\n\n if (!hasReceivedDeltasRef.current) {\n cbRef.current.onChunk(sanitizedText);\n }\n\n cbRef.current.onComplete(sanitizedText);\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = true;\n accumulatedLenRef.current = 0;\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n },\n onError: (err) => {\n console.error('[useCdecliChannel] subscription error:', err);\n cbRef.current.onError(err.message || 'CDeCLI subscription error');\n },\n });\n\n const sendMessage = useCallback(\n async (text: string, chatId = 'messenger'): Promise<boolean> => {\n if (!isConnected) return false;\n\n pendingRef.current = { text, sentAt: Date.now() };\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = false;\n accumulatedLenRef.current = 0;\n\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text,\n ...(model || skill\n ? { metadata: { ...(model && { model }), ...(skill && { skill }) } }\n : {}),\n },\n },\n });\n\n const payload = result.data?.gatewaySendMessage;\n if (!payload?.success) {\n const errMsg = payload?.error || 'CDeCLI send failed';\n cbRef.current.onError(errMsg);\n pendingRef.current = null;\n return false;\n }\n\n timeoutRef.current = setTimeout(() => {\n if (pendingRef.current) {\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n timeoutRef.current = null;\n cbRef.current.onError(\n 'CDeCLI agent did not respond within 300 seconds. The query may still be processing.',\n );\n }\n }, CDECLI_RESPONSE_TIMEOUT_MS);\n\n return true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n cbRef.current.onError(msg);\n pendingRef.current = null;\n return false;\n }\n },\n [isConnected, accountId, channelId, sendMutation, model, skill],\n );\n\n useEffect(\n () => () => {\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = false;\n accumulatedLenRef.current = 0;\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n },\n [],\n );\n\n return { sendMessage };\n}\n"],"names":[],"mappings":"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"}