@adminide-stack/yantra-mobile 12.0.69-alpha.1 → 12.0.69-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/features/agent-chat/NativeAgentChat.js +2 -0
- package/lib/features/agent-chat/NativeAgentChat.js.map +1 -1
- package/lib/features/chat/ChatCodeBlock.js +7 -3
- package/lib/features/chat/ChatCodeBlock.js.map +1 -1
- package/lib/features/chat/ConnectorConnectCard.js +164 -0
- package/lib/features/chat/ConnectorConnectCard.js.map +1 -0
- package/lib/features/chat/ConnectorConnectPrompt.js +254 -0
- package/lib/features/chat/ConnectorConnectPrompt.js.map +1 -0
- package/lib/features/chat/MessageActionBar.js.map +1 -1
- package/lib/features/chat/connectorConnect.js +194 -0
- package/lib/features/chat/connectorConnect.js.map +1 -0
- package/lib/features/chat/connectorConnectHandbacks.js +55 -0
- package/lib/features/chat/connectorConnectHandbacks.js.map +1 -0
- package/lib/features/messaging/chatOperations.js.map +1 -1
- package/lib/features/messaging/chatRegistrationStorage.js +4 -1
- package/lib/features/messaging/chatRegistrationStorage.js.map +1 -1
- package/lib/features/messaging/useYantraChatMembership.js +9 -6
- package/lib/features/messaging/useYantraChatMembership.js.map +1 -1
- package/lib/hooks/useCdecliChannel.js +2 -2
- package/lib/hooks/useCdecliChannel.js.map +1 -1
- package/lib/hooks/useChatApi.js +4 -2
- package/lib/hooks/useChatApi.js.map +1 -1
- package/lib/hooks/useHostConnectorCard.js +24 -0
- package/lib/hooks/useHostConnectorCard.js.map +1 -0
- package/lib/screens/Chat/index.js +12 -2
- package/lib/screens/Chat/index.js.map +1 -1
- package/lib/screens/JobSearchNative/useJobSearchHostChat.js +12 -6
- package/lib/screens/JobSearchNative/useJobSearchHostChat.js.map +1 -1
- package/lib/screens/ResumeNative/useResumeHostChat.js +12 -6
- package/lib/screens/ResumeNative/useResumeHostChat.js.map +1 -1
- package/lib/screens/ShoppingNative/useShoppingHostChat.js +12 -6
- package/lib/screens/ShoppingNative/useShoppingHostChat.js.map +1 -1
- package/lib/screens/TravelNative/useTravelHostChat.js +12 -6
- package/lib/screens/TravelNative/useTravelHostChat.js.map +1 -1
- package/lib/screens/WebBuilder/index.js.map +1 -1
- package/lib/services/brainQuickReply.js.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {Linking}from'react-native';import {parseConnectorConnectAppReturnUrl}from'common';const PARKED_HANDBACK_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
2
|
+
const parked = /* @__PURE__ */ new Map();
|
|
3
|
+
const activeSessions = /* @__PURE__ */ new Set();
|
|
4
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
let urlSubscription = null;
|
|
6
|
+
function parkConnectorConnectHandback(url, now = Date.now()) {
|
|
7
|
+
const handback = parseConnectorConnectAppReturnUrl(url);
|
|
8
|
+
if (!(handback == null ? void 0 : handback.requestId) || !handback.credentialId || activeSessions.has(handback.requestId)) return false;
|
|
9
|
+
parked.set(handback.requestId, {
|
|
10
|
+
handback,
|
|
11
|
+
at: now
|
|
12
|
+
});
|
|
13
|
+
listeners.forEach((listener) => listener());
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
function peekConnectorConnectHandback(requestId, now = Date.now()) {
|
|
17
|
+
const entry = parked.get(requestId);
|
|
18
|
+
if (!entry) return null;
|
|
19
|
+
if (now - entry.at > PARKED_HANDBACK_MAX_AGE_MS) {
|
|
20
|
+
parked.delete(requestId);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return entry.handback;
|
|
24
|
+
}
|
|
25
|
+
function takeConnectorConnectHandback(requestId, now = Date.now()) {
|
|
26
|
+
const handback = peekConnectorConnectHandback(requestId, now);
|
|
27
|
+
parked.delete(requestId);
|
|
28
|
+
return handback;
|
|
29
|
+
}
|
|
30
|
+
function beginConnectorConnectSession(requestId) {
|
|
31
|
+
activeSessions.add(requestId);
|
|
32
|
+
}
|
|
33
|
+
function endConnectorConnectSession(requestId) {
|
|
34
|
+
activeSessions.delete(requestId);
|
|
35
|
+
parked.delete(requestId);
|
|
36
|
+
}
|
|
37
|
+
function startListening() {
|
|
38
|
+
if (urlSubscription) return;
|
|
39
|
+
Linking.getInitialURL().then((url) => parkConnectorConnectHandback(url)).catch(() => void 0);
|
|
40
|
+
const subscription = Linking.addEventListener("url", ({
|
|
41
|
+
url
|
|
42
|
+
}) => {
|
|
43
|
+
parkConnectorConnectHandback(url);
|
|
44
|
+
});
|
|
45
|
+
urlSubscription = subscription != null ? subscription : {
|
|
46
|
+
remove: () => void 0
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function subscribeConnectorConnectHandbacks(listener) {
|
|
50
|
+
startListening();
|
|
51
|
+
listeners.add(listener);
|
|
52
|
+
return () => {
|
|
53
|
+
listeners.delete(listener);
|
|
54
|
+
};
|
|
55
|
+
}export{PARKED_HANDBACK_MAX_AGE_MS,beginConnectorConnectSession,endConnectorConnectSession,parkConnectorConnectHandback,peekConnectorConnectHandback,subscribeConnectorConnectHandbacks,takeConnectorConnectHandback};//# sourceMappingURL=connectorConnectHandbacks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectorConnectHandbacks.js","sources":["../../../src/features/chat/connectorConnectHandbacks.ts"],"sourcesContent":["/**\n * Connect hand-backs that reach the app outside a card's own browser session.\n *\n * A card opens the web vault page with `openAuthSessionAsync` and normally reads\n * the hand-back (`yantraapp://connector-connect?requestId=…&credentialId=…`) off\n * that call's result. On Android the in-app browser runs in Chrome Custom Tabs\n * with the app in the background, and the OS may kill the app there. The return\n * page then cold-starts it with the scheme URL, and no card is waiting: without\n * this, the save would never reach the agent, and the card, once back, would\n * already show the login as saved and take a later \"Update\" for no change.\n *\n * So the URL is parked here, keyed on the request it answers, until the card for\n * that request mounts and takes it (the card still checks the vault before it\n * believes it). The listener starts with the first card, which is enough: the\n * URL that launched the app stays readable through getInitialURL for the life of\n * the process. A URL for a request whose browser session is still open is left\n * to that session (on Android the same URL also arrives as a `url` event).\n */\nimport { Linking } from 'react-native';\nimport { parseConnectorConnectAppReturnUrl, type ConnectorConnectHandback } from 'common';\n\n/** A parked hand-back older than this is dropped: it belongs to a request long settled. */\nexport const PARKED_HANDBACK_MAX_AGE_MS = 30 * 60 * 1000;\n\nconst parked = new Map<string, { handback: ConnectorConnectHandback; at: number }>();\nconst activeSessions = new Set<string>();\nconst listeners = new Set<() => void>();\n/** The one app-wide Linking subscription, started by the first card (null until then). */\nlet urlSubscription: { remove: () => void } | null = null;\n\n/** Park the hand-back `url` carries, if it is one for a request no open session is waiting on. */\nexport function parkConnectorConnectHandback(url: string | null | undefined, now = Date.now()): boolean {\n const handback = parseConnectorConnectAppReturnUrl(url);\n if (!handback?.requestId || !handback.credentialId || activeSessions.has(handback.requestId)) return false;\n parked.set(handback.requestId, { handback, at: now });\n listeners.forEach((listener) => listener());\n return true;\n}\n\n/** The hand-back parked for `requestId`, if a fresh one is there; it stays parked (a stale one is dropped). */\nexport function peekConnectorConnectHandback(requestId: string, now = Date.now()): ConnectorConnectHandback | null {\n const entry = parked.get(requestId);\n if (!entry) return null;\n if (now - entry.at > PARKED_HANDBACK_MAX_AGE_MS) {\n parked.delete(requestId);\n return null;\n }\n return entry.handback;\n}\n\n/** Take (and forget) the hand-back parked for `requestId`, if a fresh one is there. */\nexport function takeConnectorConnectHandback(requestId: string, now = Date.now()): ConnectorConnectHandback | null {\n const handback = peekConnectorConnectHandback(requestId, now);\n parked.delete(requestId);\n return handback;\n}\n\n/** A card's browser session for `requestId` is open: its own result carries the hand-back. */\nexport function beginConnectorConnectSession(requestId: string): void {\n activeSessions.add(requestId);\n}\n\n/** The session ended; its result has been read, so anything parked for it meanwhile is stale. */\nexport function endConnectorConnectSession(requestId: string): void {\n activeSessions.delete(requestId);\n parked.delete(requestId);\n}\n\nfunction startListening(): void {\n if (urlSubscription) return;\n // Cold start: the hand-back that launched the app.\n Linking.getInitialURL()\n .then((url) => parkConnectorConnectHandback(url))\n .catch(() => undefined);\n // Warm: one that arrives while the app runs with no session waiting on it.\n const subscription: { remove: () => void } | undefined = Linking.addEventListener('url', ({ url }) => {\n parkConnectorConnectHandback(url);\n });\n urlSubscription = subscription ?? { remove: () => undefined };\n}\n\n/** Call `listener` whenever a hand-back is parked; starts listening for them on first use. */\nexport function subscribeConnectorConnectHandbacks(listener: () => void): () => void {\n startListening();\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/** Test seam: forget everything parked and every open session, and listen afresh (as a new process would). */\nexport function resetConnectorConnectHandbacksForTests(): void {\n parked.clear();\n activeSessions.clear();\n listeners.clear();\n urlSubscription?.remove();\n urlSubscription = null;\n}\n"],"names":[],"mappings":"0FAsBa,MAAA,0BAAA,GAA6B,KAAK,EAAK,GAAA;AACpD,MAAM,MAAA,uBAAa,GAGhB,EAAA;AACH,MAAM,cAAA,uBAAqB,GAAY,EAAA;AACvC,MAAM,SAAA,uBAAgB,GAAgB,EAAA;AAEtC,IAAI,eAEO,GAAA,IAAA;AAGJ,SAAS,4BAA6B,CAAA,GAAA,EAAgC,GAAM,GAAA,IAAA,CAAK,KAAgB,EAAA;AACtG,EAAM,MAAA,QAAA,GAAW,kCAAkC,GAAG,CAAA;AACtD,EAAI,IAAA,EAAC,QAAU,IAAA,IAAA,GAAA,MAAA,GAAA,QAAA,CAAA,SAAA,CAAA,IAAa,CAAC,QAAA,CAAS,YAAgB,IAAA,cAAA,CAAe,GAAI,CAAA,QAAA,CAAS,SAAS,CAAA,EAAU,OAAA,KAAA;AACrG,EAAO,MAAA,CAAA,GAAA,CAAI,SAAS,SAAW,EAAA;AAAA,IAC7B,QAAA;AAAA,IACA,EAAI,EAAA;AAAA,GACL,CAAA;AACD,EAAU,SAAA,CAAA,OAAA,CAAQ,CAAY,QAAA,KAAA,QAAA,EAAU,CAAA;AACxC,EAAO,OAAA,IAAA;AACT;AAGO,SAAS,4BAA6B,CAAA,SAAA,EAAmB,GAAM,GAAA,IAAA,CAAK,KAAwC,EAAA;AACjH,EAAM,MAAA,KAAA,GAAQ,MAAO,CAAA,GAAA,CAAI,SAAS,CAAA;AAClC,EAAI,IAAA,CAAC,OAAc,OAAA,IAAA;AACnB,EAAI,IAAA,GAAA,GAAM,KAAM,CAAA,EAAA,GAAK,0BAA4B,EAAA;AAC/C,IAAA,MAAA,CAAO,OAAO,SAAS,CAAA;AACvB,IAAO,OAAA,IAAA;AAAA;AAET,EAAA,OAAO,KAAM,CAAA,QAAA;AACf;AAGO,SAAS,4BAA6B,CAAA,SAAA,EAAmB,GAAM,GAAA,IAAA,CAAK,KAAwC,EAAA;AACjH,EAAM,MAAA,QAAA,GAAW,4BAA6B,CAAA,SAAA,EAAW,GAAG,CAAA;AAC5D,EAAA,MAAA,CAAO,OAAO,SAAS,CAAA;AACvB,EAAO,OAAA,QAAA;AACT;AAGO,SAAS,6BAA6B,SAAyB,EAAA;AACpE,EAAA,cAAA,CAAe,IAAI,SAAS,CAAA;AAC9B;AAGO,SAAS,2BAA2B,SAAyB,EAAA;AAClE,EAAA,cAAA,CAAe,OAAO,SAAS,CAAA;AAC/B,EAAA,MAAA,CAAO,OAAO,SAAS,CAAA;AACzB;AACA,SAAS,cAAuB,GAAA;AAC9B,EAAA,IAAI,eAAiB,EAAA;AAErB,EAAQ,OAAA,CAAA,aAAA,EAAgB,CAAA,IAAA,CAAK,CAAO,GAAA,KAAA,4BAAA,CAA6B,GAAG,CAAC,CAAA,CAAE,KAAM,CAAA,MAAM,MAAS,CAAA;AAE5F,EAAA,MAAM,YAEU,GAAA,OAAA,CAAQ,gBAAiB,CAAA,KAAA,EAAO,CAAC;AAAA,IAC/C;AAAA,GACI,KAAA;AACJ,IAAA,4BAAA,CAA6B,GAAG,CAAA;AAAA,GACjC,CAAA;AACD,EAAA,eAAA,GAAkB,YAAgB,IAAA,IAAA,GAAA,YAAA,GAAA;AAAA,IAChC,QAAQ,MAAM;AAAA,GAChB;AACF;AAGO,SAAS,mCAAmC,QAAkC,EAAA;AACnF,EAAe,cAAA,EAAA;AACf,EAAA,SAAA,CAAU,IAAI,QAAQ,CAAA;AACtB,EAAA,OAAO,MAAM;AACX,IAAA,SAAA,CAAU,OAAO,QAAQ,CAAA;AAAA,GAC3B;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chatOperations.js","sources":["../../../src/features/messaging/chatOperations.ts"],"sourcesContent":["/**\n * Every GraphQL operation the chat remote sends, as plain strings.\n *\n * Strings, not `gql` documents, because the remote must not import\n * @apollo/client or graphql - the host parses and executes these on the app's\n * own client (see `ChatGraphQLSeam`). The upside beyond bundle size is that the\n * QUERY SHAPE ships over the air: a missing field or a wrong argument is a\n * surface deploy, not an App Store release.\n *\n * HARD REQUIREMENT - every operation is NAMED, and every subscription's name\n * ends in `_WS`. `portable-devices/mobile/src/config/base-apollo-client.ts`\n * routes ws-vs-http with `operationName.endsWith('_WS')`; an anonymous\n * operation makes that `undefined.endsWith(...)`, which throws for EVERY\n * operation the app sends, not just this one. `chatOperations.test.ts` asserts\n * both properties over every export in this file, so a new operation cannot\n * quietly break the app's transport.\n */\n\n/**\n * Shared field set for a message.\n *\n * `author` and `creator` are both selected on purpose: messenger-box populates\n * `author` on some write paths and `creator` on others (`sendMessage` sets\n * creator), and a transcript that reads only one shows \"Unknown\" for half the\n * messages. `chatModel.pickAuthor` collapses them.\n */\nexport const POST_FIELDS = `\n id\n message\n type\n createdAt\n updatedAt\n editedAt\n deletedAt\n rootId\n parentId\n filenames\n props\n author { id givenName familyName username email picture }\n creator { id givenName familyName username email picture }\n`;\n\nconst CHANNEL_FIELDS = `\n id\n type\n orgName\n title\n displayName\n topic\n lastPostAt\n totalMsgCount\n createdAt\n lastMessage { ${POST_FIELDS} }\n creator { id givenName familyName username email picture }\n members { id user { id givenName familyName username email picture } msgCount lastViewedAt }\n`;\n\n/**\n * The conversation list.\n *\n * `criteria.members.$elemMatch.user` is NOT sent: the server derives membership\n * from the session (`accountId`) and a client-supplied member filter is exactly\n * the kind of thing that lets a caller read someone else's list. Org scoping is\n * likewise server-side now (`channelOrgScope`). Native Messages passes\n * `criteria: { type: DIRECT, orgName: yantra-chat }` via `nativeChatChannelQuery`.\n */\nexport const CHANNELS_QUERY = `\n query ChatChannels($limit: Int, $skip: Int, $criteria: AnyObject, $sort: Sort) {\n channelsByUser(limit: $limit, skip: $skip, criteria: $criteria, sort: $sort) {\n ${CHANNEL_FIELDS}\n }\n }\n`;\n\n/**\n * One page of a transcript, newest first.\n *\n * Paged with `skip`, which is what the server exposes. That is offset paging and\n * it can repeat or drop a row if messages arrive mid-scroll; `chatModel.mergePosts`\n * de-duplicates by id so the visible effect is bounded. A cursor (`before: id`)\n * would be strictly better and is worth adding server-side later.\n */\nexport const MESSAGES_QUERY = `\n query ChatMessages($channelId: ID, $limit: Int! = 30, $skip: Int! = 0, $sort: Sort) {\n messages(channelId: $channelId, limit: $limit, skip: $skip, sort: $sort) {\n totalCount\n data {\n ${POST_FIELDS}\n }\n }\n }\n`;\n\nexport const SEND_MESSAGE_MUTATION = `\n mutation ChatSendMessage($channelId: String!, $messageInput: MessageInput!) {\n sendMessage(channelId: $channelId, messageInput: $messageInput) {\n ${POST_FIELDS}\n }\n }\n`;\n\n/** Marks the channel read for the caller; drives the unread badge to zero. */\nexport const VIEW_CHANNEL_MUTATION = `\n mutation ChatViewChannel($id: ID!) {\n viewChannel(id: $id)\n }\n`;\n\n/**\n * Opens (or returns) the 1:1 conversation with `receiver`.\n *\n * Idempotent server-side: an existing DM for the pair comes back rather than a\n * duplicate, so the UI can call this every time someone taps a contact without\n * checking first.\n */\nexport const CREATE_DIRECT_CHANNEL_MUTATION = `\n mutation ChatCreateDirectChannel($receiver: [ID]!, $displayName: String!) {\n createDirectChannel(receiver: $receiver, displayName: $displayName) {\n ${CHANNEL_FIELDS}\n }\n }\n`;\n\n/**\n * Live messages for one channel.\n *\n * `_WS` suffix is load-bearing - see the file header. `directTo` is left unset:\n * the server treats it as a second, user-scoped topic and passing the wrong one\n * silently delivers nothing.\n */\nexport const MESSAGE_ADDED_SUBSCRIPTION_WS = `\n subscription ChatMessageAdded_WS($channelId: String) {\n chatMessageAdded(channelId: $channelId) {\n ${POST_FIELDS}\n }\n }\n`;\n\n/**\n * Which of these phone numbers already have an account.\n *\n * Sends SHA-256 hashes of E.164 numbers, never the numbers - the server never\n * receives a plaintext address book. Returns AnyObject: a list of\n * `{phoneHash, userId, username, displayName, picture}`.\n */\nexport const CONTACT_MATCHES_QUERY = `\n query ChatContactMatches($phoneHashes: [String!]!) {\n chatContactMatches(phoneHashes: $phoneHashes)\n }\n`;\n\nexport const YANTRA_CHAT_REGISTRATION_STATUS_QUERY = `\n query YantraChatRegistrationStatus {\n yantraChatRegistrationStatus {\n registered\n phoneE164\n memberId\n organizationId\n }\n }\n`;\n\nexport const REGISTER_YANTRA_CHAT_MEMBER_MUTATION = `\n mutation RegisterYantraChatMember($phoneE164: String!, $displayName: String) {\n registerYantraChatMember(phoneE164: $phoneE164, displayName: $displayName) {\n registered\n phoneE164\n memberId\n organizationId\n }\n }\n`;\n\nexport const MATCH_YANTRA_CHAT_MEMBERS_QUERY = `\n query MatchYantraChatMembersByPhone($phones: [String!]!, $viewerPhoneE164: String) {\n matchYantraChatMembersByPhone(phones: $phones, viewerPhoneE164: $viewerPhoneE164) {\n phoneE164\n userId\n displayName\n avatarUrl\n }\n }\n`;\n\nexport const YANTRA_CHAT_MEMBERS_QUERY = `\n query YantraChatMembers {\n yantraChatMembers {\n phoneE164\n userId\n displayName\n avatarUrl\n }\n }\n`;\n\n/** Composer draft persistence, server-side and per (member, channel). */\nexport const SET_DRAFT_MUTATION = `\n mutation ChatSetDraft($channelId: ID!, $text: String!) {\n setChannelDraft(channelId: $channelId, text: $text)\n }\n`;\n\nexport const DRAFT_QUERY = `\n query ChatDraft($channelId: ID!) {\n channelDraft(channelId: $channelId)\n }\n`;\n\n/** Every operation, for the contract test and for host-side preflight. */\nexport const CHAT_OPERATIONS: Readonly<Record<string, string>> = Object.freeze({\n CHANNELS_QUERY,\n CONTACT_MATCHES_QUERY,\n YANTRA_CHAT_REGISTRATION_STATUS_QUERY,\n REGISTER_YANTRA_CHAT_MEMBER_MUTATION,\n MATCH_YANTRA_CHAT_MEMBERS_QUERY,\n YANTRA_CHAT_MEMBERS_QUERY,\n MESSAGES_QUERY,\n SEND_MESSAGE_MUTATION,\n VIEW_CHANNEL_MUTATION,\n CREATE_DIRECT_CHANNEL_MUTATION,\n MESSAGE_ADDED_SUBSCRIPTION_WS,\n SET_DRAFT_MUTATION,\n DRAFT_QUERY,\n});\n\n/**\n * The operation name inside a document string.\n *\n * Used by the contract test and by the host seam for logging. Intentionally a\n * regex rather than a parse: pulling in `graphql` to read a name would defeat\n * the whole point of shipping strings.\n */\nexport function operationNameOf(document: string): string | null {\n const match = /\\b(?:query|mutation|subscription)\\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(document);\n return match ? match[1] : null;\n}\n\nexport function operationTypeOf(document: string): 'query' | 'mutation' | 'subscription' | null {\n const match = /\\b(query|mutation|subscription)\\s+[A-Za-z_]/.exec(document);\n return match ? (match[1] as 'query' | 'mutation' | 'subscription') : null;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chatOperations.js","sources":["../../../src/features/messaging/chatOperations.ts"],"sourcesContent":["/**\n * Every GraphQL operation the chat remote sends, as plain strings.\n *\n * Strings, not `gql` documents, because the remote must not import\n * @apollo/client or graphql - the host parses and executes these on the app's\n * own client (see `ChatGraphQLSeam`). The upside beyond bundle size is that the\n * QUERY SHAPE ships over the air: a missing field or a wrong argument is a\n * surface deploy, not an App Store release.\n *\n * HARD REQUIREMENT - every operation is NAMED, and every subscription's name\n * ends in `_WS`. `portable-devices/mobile/src/config/base-apollo-client.ts`\n * routes ws-vs-http with `operationName.endsWith('_WS')`; an anonymous\n * operation makes that `undefined.endsWith(...)`, which throws for EVERY\n * operation the app sends, not just this one. `chatOperations.test.ts` asserts\n * both properties over every export in this file, so a new operation cannot\n * quietly break the app's transport.\n */\n\n/**\n * Shared field set for a message.\n *\n * `author` and `creator` are both selected on purpose: messenger-box populates\n * `author` on some write paths and `creator` on others (`sendMessage` sets\n * creator), and a transcript that reads only one shows \"Unknown\" for half the\n * messages. `chatModel.pickAuthor` collapses them.\n */\nexport const POST_FIELDS = `\n id\n message\n type\n createdAt\n updatedAt\n editedAt\n deletedAt\n rootId\n parentId\n filenames\n props\n author { id givenName familyName username email picture }\n creator { id givenName familyName username email picture }\n reactionSummary { reaction count userIds }\n`;\n\nconst CHANNEL_FIELDS = `\n id\n type\n orgName\n title\n displayName\n topic\n lastPostAt\n totalMsgCount\n createdAt\n lastMessage { ${POST_FIELDS} }\n creator { id givenName familyName username email picture }\n members { id user { id givenName familyName username email picture } msgCount lastViewedAt }\n`;\n\n/**\n * The conversation list.\n *\n * `criteria.members.$elemMatch.user` is NOT sent: the server derives membership\n * from the session (`accountId`) and a client-supplied member filter is exactly\n * the kind of thing that lets a caller read someone else's list. Org scoping is\n * likewise server-side now (`channelOrgScope`). Native Messages passes\n * `criteria: { type: DIRECT, orgName: yantra-chat }` via `nativeChatChannelQuery`.\n */\nexport const CHANNELS_QUERY = `\n query ChatChannels($limit: Int, $skip: Int, $criteria: AnyObject, $sort: Sort) {\n channelsByUser(limit: $limit, skip: $skip, criteria: $criteria, sort: $sort) {\n ${CHANNEL_FIELDS}\n }\n }\n`;\n\n/**\n * One page of a transcript, newest first.\n *\n * Paged with `skip`, which is what the server exposes. That is offset paging and\n * it can repeat or drop a row if messages arrive mid-scroll; `chatModel.mergePosts`\n * de-duplicates by id so the visible effect is bounded. A cursor (`before: id`)\n * would be strictly better and is worth adding server-side later.\n */\nexport const MESSAGES_QUERY = `\n query ChatMessages($channelId: ID, $limit: Int! = 30, $skip: Int! = 0, $sort: Sort) {\n messages(channelId: $channelId, limit: $limit, skip: $skip, sort: $sort) {\n totalCount\n data {\n ${POST_FIELDS}\n }\n }\n }\n`;\n\nexport const SEND_MESSAGE_MUTATION = `\n mutation ChatSendMessage($channelId: String!, $messageInput: MessageInput!) {\n sendMessage(channelId: $channelId, messageInput: $messageInput) {\n ${POST_FIELDS}\n }\n }\n`;\n\n/** Marks the channel read for the caller; drives the unread badge to zero. */\nexport const VIEW_CHANNEL_MUTATION = `\n mutation ChatViewChannel($id: ID!) {\n viewChannel(id: $id)\n }\n`;\n\n/**\n * Opens (or returns) the 1:1 conversation with `receiver`.\n *\n * Idempotent server-side: an existing DM for the pair comes back rather than a\n * duplicate, so the UI can call this every time someone taps a contact without\n * checking first.\n */\nexport const CREATE_DIRECT_CHANNEL_MUTATION = `\n mutation ChatCreateDirectChannel($receiver: [ID]!, $displayName: String!) {\n createDirectChannel(receiver: $receiver, displayName: $displayName) {\n ${CHANNEL_FIELDS}\n }\n }\n`;\n\n/**\n * Live messages for one channel.\n *\n * `_WS` suffix is load-bearing - see the file header. `directTo` is left unset:\n * the server treats it as a second, user-scoped topic and passing the wrong one\n * silently delivers nothing.\n */\nexport const MESSAGE_ADDED_SUBSCRIPTION_WS = `\n subscription ChatMessageAdded_WS($channelId: String) {\n chatMessageAdded(channelId: $channelId) {\n ${POST_FIELDS}\n }\n }\n`;\n\n/**\n * Set the viewer's single Tapback on a message; `reaction: null` removes it.\n * Returns the message's reactions after the change.\n */\nexport const SET_MESSAGE_REACTION_MUTATION = `\n mutation ChatSetMessageReaction($channelId: String!, $postId: ID!, $reaction: String) {\n setMessageReaction(channelId: $channelId, postId: $postId, reaction: $reaction) {\n reaction\n count\n userIds\n }\n }\n`;\n\n/** Live reaction changes for one channel. `_WS` suffix is load-bearing - see the file header. */\nexport const REACTION_CHANGED_SUBSCRIPTION_WS = `\n subscription ChatReactionChanged_WS($channelId: String!) {\n chatReactionChanged(channelId: $channelId) {\n channelId\n postId\n reactions { reaction count userIds }\n }\n }\n`;\n\n/**\n * Which of these phone numbers already have an account.\n *\n * Sends SHA-256 hashes of E.164 numbers, never the numbers - the server never\n * receives a plaintext address book. Returns AnyObject: a list of\n * `{phoneHash, userId, username, displayName, picture}`.\n */\nexport const CONTACT_MATCHES_QUERY = `\n query ChatContactMatches($phoneHashes: [String!]!) {\n chatContactMatches(phoneHashes: $phoneHashes)\n }\n`;\n\nexport const YANTRA_CHAT_REGISTRATION_STATUS_QUERY = `\n query YantraChatRegistrationStatus {\n yantraChatRegistrationStatus {\n registered\n phoneE164\n memberId\n organizationId\n }\n }\n`;\n\nexport const REGISTER_YANTRA_CHAT_MEMBER_MUTATION = `\n mutation RegisterYantraChatMember($phoneE164: String!, $displayName: String) {\n registerYantraChatMember(phoneE164: $phoneE164, displayName: $displayName) {\n registered\n phoneE164\n memberId\n organizationId\n }\n }\n`;\n\nexport const MATCH_YANTRA_CHAT_MEMBERS_QUERY = `\n query MatchYantraChatMembersByPhone($phones: [String!]!, $viewerPhoneE164: String) {\n matchYantraChatMembersByPhone(phones: $phones, viewerPhoneE164: $viewerPhoneE164) {\n phoneE164\n userId\n displayName\n avatarUrl\n }\n }\n`;\n\nexport const YANTRA_CHAT_MEMBERS_QUERY = `\n query YantraChatMembers {\n yantraChatMembers {\n phoneE164\n userId\n displayName\n avatarUrl\n }\n }\n`;\n\n/** Composer draft persistence, server-side and per (member, channel). */\nexport const SET_DRAFT_MUTATION = `\n mutation ChatSetDraft($channelId: ID!, $text: String!) {\n setChannelDraft(channelId: $channelId, text: $text)\n }\n`;\n\nexport const DRAFT_QUERY = `\n query ChatDraft($channelId: ID!) {\n channelDraft(channelId: $channelId)\n }\n`;\n\n/** Every operation, for the contract test and for host-side preflight. */\nexport const CHAT_OPERATIONS: Readonly<Record<string, string>> = Object.freeze({\n CHANNELS_QUERY,\n CONTACT_MATCHES_QUERY,\n YANTRA_CHAT_REGISTRATION_STATUS_QUERY,\n REGISTER_YANTRA_CHAT_MEMBER_MUTATION,\n MATCH_YANTRA_CHAT_MEMBERS_QUERY,\n YANTRA_CHAT_MEMBERS_QUERY,\n MESSAGES_QUERY,\n SEND_MESSAGE_MUTATION,\n VIEW_CHANNEL_MUTATION,\n CREATE_DIRECT_CHANNEL_MUTATION,\n MESSAGE_ADDED_SUBSCRIPTION_WS,\n SET_MESSAGE_REACTION_MUTATION,\n REACTION_CHANGED_SUBSCRIPTION_WS,\n SET_DRAFT_MUTATION,\n DRAFT_QUERY,\n});\n\n/**\n * The operation name inside a document string.\n *\n * Used by the contract test and by the host seam for logging. Intentionally a\n * regex rather than a parse: pulling in `graphql` to read a name would defeat\n * the whole point of shipping strings.\n */\nexport function operationNameOf(document: string): string | null {\n const match = /\\b(?:query|mutation|subscription)\\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(document);\n return match ? match[1] : null;\n}\n\nexport function operationTypeOf(document: string): 'query' | 'mutation' | 'subscription' | null {\n const match = /\\b(query|mutation|subscription)\\s+[A-Za-z_]/.exec(document);\n return match ? (match[1] as 'query' | 'mutation' | 'subscription') : null;\n}\n"],"names":[],"mappings":"AA8KO,MAAM,qCAAwC,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAU9C,MAAM,oCAAuC,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA"}
|
|
@@ -1 +1,4 @@
|
|
|
1
|
-
const YANTRA_CHAT_PHONE_STORAGE_KEY = "yantra-chat-registered-phone-e164";
|
|
1
|
+
const YANTRA_CHAT_PHONE_STORAGE_KEY = "yantra-chat-registered-phone-e164";
|
|
2
|
+
function yantraChatPhoneStorageKey(userId) {
|
|
3
|
+
return `${YANTRA_CHAT_PHONE_STORAGE_KEY}:${userId}`;
|
|
4
|
+
}export{YANTRA_CHAT_PHONE_STORAGE_KEY,yantraChatPhoneStorageKey};//# sourceMappingURL=chatRegistrationStorage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chatRegistrationStorage.js","sources":["../../../src/features/messaging/chatRegistrationStorage.ts"],"sourcesContent":["/**\n * AsyncStorage cache for Yantra Chat registration (issue #1312).\n * Mongo `organizationMembers` is the source of truth (`yantraChatRegistrationStatus`);\n * this key only avoids flashing the registration screen while the status query runs.\n * It is not cleared on network/GraphQL errors (Metro reload, backend restart).\n */\nexport const YANTRA_CHAT_PHONE_STORAGE_KEY = 'yantra-chat-registered-phone-e164';\n"],"names":[],"mappings":"AAMO,MAAM,6BAAgC,GAAA"}
|
|
1
|
+
{"version":3,"file":"chatRegistrationStorage.js","sources":["../../../src/features/messaging/chatRegistrationStorage.ts"],"sourcesContent":["/**\n * AsyncStorage cache for Yantra Chat registration (issue #1312).\n * Mongo `organizationMembers` is the source of truth (`yantraChatRegistrationStatus`);\n * this key only avoids flashing the registration screen while the status query runs.\n * It is not cleared on network/GraphQL errors (Metro reload, backend restart).\n */\nexport const YANTRA_CHAT_PHONE_STORAGE_KEY = 'yantra-chat-registered-phone-e164';\n\n/** Per account, so a cached registration is never trusted for a different user on the same device. */\nexport function yantraChatPhoneStorageKey(userId: string): string {\n return `${YANTRA_CHAT_PHONE_STORAGE_KEY}:${userId}`;\n}\n"],"names":[],"mappings":"AAMO,MAAM,6BAAgC,GAAA;AAGtC,SAAS,0BAA0B,MAAwB,EAAA;AAChE,EAAO,OAAA,CAAA,EAAG,6BAA6B,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AACnD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {useState,useCallback,useEffect,useMemo}from'react';import {useApolloClient,gql}from'@apollo/client/index.js';import AsyncStorage from'@react-native-async-storage/async-storage';import {YANTRA_CHAT_REGISTRATION_STATUS_QUERY,REGISTER_YANTRA_CHAT_MEMBER_MUTATION}from'./chatOperations.js';import {
|
|
1
|
+
import {useState,useCallback,useEffect,useMemo}from'react';import {useApolloClient,gql}from'@apollo/client/index.js';import AsyncStorage from'@react-native-async-storage/async-storage';import {YANTRA_CHAT_REGISTRATION_STATUS_QUERY,REGISTER_YANTRA_CHAT_MEMBER_MUTATION}from'./chatOperations.js';import {yantraChatPhoneStorageKey}from'./chatRegistrationStorage.js';const REGISTRATION_STATUS_DOCUMENT = gql(YANTRA_CHAT_REGISTRATION_STATUS_QUERY);
|
|
2
2
|
const REGISTER_MEMBER_DOCUMENT = gql(REGISTER_YANTRA_CHAT_MEMBER_MUTATION);
|
|
3
3
|
function useYantraChatMembership(userId, displayName) {
|
|
4
4
|
const client = useApolloClient();
|
|
@@ -14,8 +14,11 @@ function useYantraChatMembership(userId, displayName) {
|
|
|
14
14
|
}
|
|
15
15
|
setError(null);
|
|
16
16
|
try {
|
|
17
|
-
const cached = await AsyncStorage.getItem(
|
|
18
|
-
if (cached)
|
|
17
|
+
const cached = await AsyncStorage.getItem(yantraChatPhoneStorageKey(userId));
|
|
18
|
+
if (cached) {
|
|
19
|
+
setPhoneE164(cached);
|
|
20
|
+
setLoading(false);
|
|
21
|
+
}
|
|
19
22
|
const res = await client.query({
|
|
20
23
|
query: REGISTRATION_STATUS_DOCUMENT,
|
|
21
24
|
fetchPolicy: "network-only"
|
|
@@ -28,10 +31,10 @@ function useYantraChatMembership(userId, displayName) {
|
|
|
28
31
|
if ((status == null ? void 0 : status.registered) && status.phoneE164) {
|
|
29
32
|
setPhoneE164(status.phoneE164);
|
|
30
33
|
setOrganizationId(status.organizationId);
|
|
31
|
-
await AsyncStorage.setItem(
|
|
34
|
+
await AsyncStorage.setItem(yantraChatPhoneStorageKey(userId), status.phoneE164);
|
|
32
35
|
} else if (status && status.registered === false) {
|
|
33
36
|
setPhoneE164(null);
|
|
34
|
-
await AsyncStorage.removeItem(
|
|
37
|
+
await AsyncStorage.removeItem(yantraChatPhoneStorageKey(userId));
|
|
35
38
|
}
|
|
36
39
|
} catch (err) {
|
|
37
40
|
setError(err instanceof Error ? err.message : "Could not load chat registration");
|
|
@@ -59,7 +62,7 @@ function useYantraChatMembership(userId, displayName) {
|
|
|
59
62
|
}
|
|
60
63
|
setPhoneE164(payload.phoneE164);
|
|
61
64
|
setOrganizationId(payload.organizationId);
|
|
62
|
-
await AsyncStorage.setItem(
|
|
65
|
+
await AsyncStorage.setItem(yantraChatPhoneStorageKey(userId), payload.phoneE164);
|
|
63
66
|
return payload.phoneE164;
|
|
64
67
|
}, [client, userId, displayName]);
|
|
65
68
|
const needsRegistration = useMemo(() => Boolean(userId) && !loading && !phoneE164, [userId, loading, phoneE164]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useYantraChatMembership.js","sources":["../../../src/features/messaging/useYantraChatMembership.ts"],"sourcesContent":["/**\n * Yantra Chat registration status + register mutation (issue #1312).\n */\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { gql, useApolloClient } from '@apollo/client';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { REGISTER_YANTRA_CHAT_MEMBER_MUTATION, YANTRA_CHAT_REGISTRATION_STATUS_QUERY } from './chatOperations';\nimport {
|
|
1
|
+
{"version":3,"file":"useYantraChatMembership.js","sources":["../../../src/features/messaging/useYantraChatMembership.ts"],"sourcesContent":["/**\n * Yantra Chat registration status + register mutation (issue #1312).\n */\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { gql, useApolloClient } from '@apollo/client';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { REGISTER_YANTRA_CHAT_MEMBER_MUTATION, YANTRA_CHAT_REGISTRATION_STATUS_QUERY } from './chatOperations';\nimport { yantraChatPhoneStorageKey } from './chatRegistrationStorage';\n\nconst REGISTRATION_STATUS_DOCUMENT = gql(YANTRA_CHAT_REGISTRATION_STATUS_QUERY);\nconst REGISTER_MEMBER_DOCUMENT = gql(REGISTER_YANTRA_CHAT_MEMBER_MUTATION);\n\ninterface RegistrationPayload {\n registered: boolean;\n phoneE164: string | null;\n memberId: string | null;\n organizationId: string;\n}\n\nexport function useYantraChatMembership(userId: string | null, displayName: string) {\n const client = useApolloClient();\n const [phoneE164, setPhoneE164] = useState<string | null>(null);\n const [organizationId, setOrganizationId] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const refresh = useCallback(async () => {\n if (!userId) {\n setLoading(false);\n return;\n }\n setError(null);\n try {\n const cached = await AsyncStorage.getItem(yantraChatPhoneStorageKey(userId));\n if (cached) {\n // Already registered on this device: open chat now and confirm with the server in the\n // background. A server \"not registered\" still clears this and shows registration.\n setPhoneE164(cached);\n setLoading(false);\n }\n\n const res = await client.query<{ yantraChatRegistrationStatus?: RegistrationPayload }>({\n query: REGISTRATION_STATUS_DOCUMENT,\n fetchPolicy: 'network-only',\n });\n\n if (res.errors?.length) {\n setError(res.errors[0]?.message ?? 'Could not load chat registration');\n // Keep AsyncStorage + in-memory phone on transient GraphQL errors (Metro reload, backend restart).\n return;\n }\n\n const status = res.data?.yantraChatRegistrationStatus;\n if (status?.registered && status.phoneE164) {\n setPhoneE164(status.phoneE164);\n setOrganizationId(status.organizationId);\n await AsyncStorage.setItem(yantraChatPhoneStorageKey(userId), status.phoneE164);\n } else if (status && status.registered === false) {\n setPhoneE164(null);\n await AsyncStorage.removeItem(yantraChatPhoneStorageKey(userId));\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Could not load chat registration');\n // Network failure: do not clear cached registration.\n } finally {\n setLoading(false);\n }\n }, [client, userId]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const register = useCallback(\n async (phone: string) => {\n if (!userId) throw new Error('Not signed in');\n setError(null);\n const res = await client.mutate<{ registerYantraChatMember?: RegistrationPayload }>({\n mutation: REGISTER_MEMBER_DOCUMENT,\n variables: { phoneE164: phone, displayName },\n });\n const payload = res.data?.registerYantraChatMember;\n if (!payload?.registered || !payload.phoneE164) {\n throw new Error('Registration did not complete');\n }\n setPhoneE164(payload.phoneE164);\n setOrganizationId(payload.organizationId);\n await AsyncStorage.setItem(yantraChatPhoneStorageKey(userId), payload.phoneE164);\n return payload.phoneE164;\n },\n [client, userId, displayName],\n );\n\n const needsRegistration = useMemo(() => Boolean(userId) && !loading && !phoneE164, [userId, loading, phoneE164]);\n\n return {\n phoneE164,\n organizationId,\n loading,\n error,\n needsRegistration,\n register,\n refresh,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {useRef,useCallback,useEffect}from'react';import {AppState}from'react-native';import {useApolloClient}from'@apollo/client/index.js';import {quickReplyViaBrain}from'../services/brainQuickReply.js';import {stripModelCostHeader,MESSENGER_ACTIVE_STREAM_QUERY}from'./messengerStream.js';import {CHAT_RUN_TIMEOUT_TEXT,EMPTY_CHAT_RESPONSE_TEXT,resolveAssistantReply,CDECLI_MAX_RUN_MS}from'../features/chat/emptyChatResponse.js';import {splitPreResponse,isFastLaneRunId}from'../features/chat/preResponse.js';import {useGatewaySendMessageMutation,useMessengerStreamDeltaSubscription,useGatewayInboundMessageByChannelSubscription}from'common/graphql';var __defProp = Object.defineProperty;
|
|
1
|
+
import {useRef,useCallback,useEffect}from'react';import {AppState}from'react-native';import {useApolloClient}from'@apollo/client/index.js';import {isConnectorReply}from'common';import {quickReplyViaBrain}from'../services/brainQuickReply.js';import {stripModelCostHeader,MESSENGER_ACTIVE_STREAM_QUERY}from'./messengerStream.js';import {CHAT_RUN_TIMEOUT_TEXT,EMPTY_CHAT_RESPONSE_TEXT,resolveAssistantReply,CDECLI_MAX_RUN_MS}from'../features/chat/emptyChatResponse.js';import {splitPreResponse,isFastLaneRunId}from'../features/chat/preResponse.js';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;
|
|
@@ -251,7 +251,7 @@ function useCdecliChannel(isConnected, accountId, channelId, callbacks, model, s
|
|
|
251
251
|
lastRunIdRef.current = null;
|
|
252
252
|
quickReplyShownRef.current = false;
|
|
253
253
|
const turn = ++turnSeqRef.current;
|
|
254
|
-
if (media.length === 0) {
|
|
254
|
+
if (media.length === 0 && !isConnectorReply(text)) {
|
|
255
255
|
void quickReplyViaBrain(text).then((answer) => {
|
|
256
256
|
var _a2, _b;
|
|
257
257
|
if (!answer) return;
|
|
@@ -1 +1 @@
|
|
|
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, chatId, media)` 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 { AppState } from 'react-native';\nimport { useApolloClient } from '@apollo/client';\nimport { quickReplyViaBrain } from '../services/brainQuickReply';\nimport { MESSENGER_ACTIVE_STREAM_QUERY, stripModelCostHeader } from './messengerStream';\nimport {\n CHAT_RUN_TIMEOUT_TEXT,\n CDECLI_MAX_RUN_MS,\n EMPTY_CHAT_RESPONSE_TEXT,\n resolveAssistantReply,\n} from '../features/chat/emptyChatResponse';\nimport { isFastLaneRunId, splitPreResponse } from '../features/chat/preResponse';\nimport {\n useGatewaySendMessageMutation,\n useGatewayInboundMessageByChannelSubscription,\n useMessengerStreamDeltaSubscription,\n} from 'common/graphql';\n\n// `error?: undefined` on the success arm keeps `result.error` reachable on the\n// union: with strictNullChecks off (repo-wide), TS will not narrow the\n// discriminated union after an `if (result.ok) continue`.\nexport type SteerResult = { ok: true; error?: undefined } | { ok: false; error: string };\n\nexport interface CdecliChannelCallbacks {\n onChunk: (text: string) => void;\n /**\n * A fast provisional answer from `yantra-brain` or `CDECLI_FAST_LANE=pre`.\n * Shown as a Quick answer strip — never the main bubble. Pass the full\n * text so far (not a delta). Do not pass `''` to hide it when the agent\n * starts; web keeps the strip beside the agent reply.\n */\n onQuickReply?: (text: string) => void;\n /**\n * The agent started a new run (typically after the fast lane). Clear the\n * main bubble so the agent's tokens do not append onto the provisional\n * article.\n */\n onStreamRestart?: () => void;\n onComplete: (text: string) => void;\n onError: (error: string) => void;\n}\n\nexport function isNoActiveSessionError(message: string): boolean {\n return /no active session/i.test(message);\n}\n\n/**\n * Idle window, not a total budget. The timer is re-armed on every streamed\n * delta (a turn that is actively streaming is not stuck), and paused while the\n * app is backgrounded (iOS suspends the WebSocket + throttles JS timers, so a\n * wall-clock timer would otherwise fire a false \"did not respond\" the instant\n * the app returns to the foreground). It fires only after this much CONTINUOUS\n * silence with the app in the foreground.\n */\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; chatId: string } | null>(null);\n const hasReceivedDeltasRef = useRef(false);\n const reconnectedStreamRef = useRef(false);\n const streamCompletedRef = useRef(false);\n const accumulatedLenRef = useRef(0);\n const lastRunIdRef = useRef<string | null>(null);\n const apolloClient = useApolloClient();\n const seededChannelRef = useRef<string | null>(null);\n const quickReplyShownRef = useRef(false);\n\n /**\n * Forward newly-accumulated stream text. Shared by live deltas and the\n * mount-time resume seed so neither gaps nor double-delivery happen when\n * the user leaves Chat for history and comes back mid-turn.\n */\n const ingestAccumulatedText = useCallback((accumulated: string, asFastLane = false) => {\n const stripped = stripModelCostHeader(accumulated);\n const { preResponse, text: fullText } = splitPreResponse(stripped);\n if (preResponse) {\n quickReplyShownRef.current = true;\n cbRef.current.onQuickReply?.(preResponse);\n }\n if (asFastLane) {\n const fast = (preResponse || fullText).trim();\n if (fast) {\n quickReplyShownRef.current = true;\n cbRef.current.onQuickReply?.(fast);\n }\n return;\n }\n const newChunk = fullText.substring(accumulatedLenRef.current);\n accumulatedLenRef.current = fullText.length;\n if (newChunk) cbRef.current.onChunk(newChunk);\n }, []);\n // Identity of the current turn for the brain race. A counter, not a\n // timestamp: two sends can share a millisecond, and a timestamp would then\n // let a brain answer for a superseded turn paint over the live one.\n const turnSeqRef = useRef(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const maxRunTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const runStartedAtRef = useRef<number | null>(null);\n const stoppingRef = useRef(false);\n\n const streamSkip = !channelId;\n const streamChannelId = channelId || accountId;\n\n const clearRunTimers = useCallback(() => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n if (maxRunTimerRef.current) {\n clearTimeout(maxRunTimerRef.current);\n maxRunTimerRef.current = null;\n }\n }, []);\n\n const stopTurn = useCallback(\n (reason: 'idle' | 'max-run') => {\n const pending = pendingRef.current;\n if (!pending || stoppingRef.current) return;\n stoppingRef.current = true;\n const chatId = pending.chatId;\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n streamCompletedRef.current = true;\n runStartedAtRef.current = null;\n clearRunTimers();\n void sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text: '',\n metadata: { cancel: true },\n },\n },\n }).catch(() => {\n /* local stop still lands a standard reply */\n });\n cbRef.current.onComplete(reason === 'max-run' ? CHAT_RUN_TIMEOUT_TEXT : EMPTY_CHAT_RESPONSE_TEXT);\n stoppingRef.current = false;\n },\n [accountId, clearRunTimers, sendMutation],\n );\n\n /** (Re)start the idle timer for the in-flight turn. No-op if none pending. */\n const armTimeout = useCallback(() => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n if (!pendingRef.current) {\n timeoutRef.current = null;\n return;\n }\n timeoutRef.current = setTimeout(() => stopTurn('idle'), CDECLI_RESPONSE_TIMEOUT_MS);\n }, [stopTurn]);\n\n /** Hard 2-minute cap from send start — still-streaming turns are cancelled too. */\n const armMaxRun = useCallback(() => {\n if (maxRunTimerRef.current) clearTimeout(maxRunTimerRef.current);\n const started = runStartedAtRef.current;\n if (!pendingRef.current || started == null) {\n maxRunTimerRef.current = null;\n return;\n }\n const remaining = CDECLI_MAX_RUN_MS - (Date.now() - started);\n maxRunTimerRef.current = setTimeout(() => stopTurn('max-run'), Math.max(0, remaining));\n }, [stopTurn]);\n\n // Pause JS timers in the background; on resume re-arm from wall-clock start\n // so a 2-minute run that finished while away is stopped immediately.\n useEffect(() => {\n const sub = AppState.addEventListener('change', (next) => {\n if (next === 'active') {\n if (pendingRef.current && !streamCompletedRef.current) {\n armTimeout();\n armMaxRun();\n }\n } else {\n clearRunTimers();\n }\n });\n return () => sub.remove();\n }, [armMaxRun, armTimeout, clearRunTimers]);\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 const prevRunId = lastRunIdRef.current;\n if (delta.runId && delta.runId !== prevRunId) {\n accumulatedLenRef.current = 0;\n // Fast-lane run (`fast-…`) then agent (`serve-…`): start a fresh\n // bubble instead of appending the agent onto the provisional article.\n if (prevRunId && !isFastLaneRunId(delta.runId)) {\n cbRef.current.onStreamRestart?.();\n }\n }\n if (delta.runId) lastRunIdRef.current = delta.runId;\n\n if (streamCompletedRef.current) return;\n\n // Pick up a turn that kept running after the user left Chat for history.\n if (!pendingRef.current) {\n if (!reconnectedStreamRef.current) {\n reconnectedStreamRef.current = true;\n accumulatedLenRef.current = 0;\n }\n }\n\n hasReceivedDeltasRef.current = true;\n armTimeout();\n ingestAccumulatedText(delta.text ?? '', isFastLaneRunId(delta.runId));\n },\n onError: (err) => {\n console.error('[useCdecliChannel] stream delta subscription error:', err);\n },\n });\n\n // Seed the in-flight reply when Chat remounts (history → chat) mid-stream.\n useEffect(() => {\n if (streamSkip || !streamChannelId) return undefined;\n if (seededChannelRef.current === streamChannelId) return undefined;\n seededChannelRef.current = streamChannelId;\n accumulatedLenRef.current = 0;\n streamCompletedRef.current = false;\n reconnectedStreamRef.current = false;\n lastRunIdRef.current = null;\n\n let cancelled = false;\n apolloClient\n .query({\n query: MESSENGER_ACTIVE_STREAM_QUERY,\n variables: { channelId: streamChannelId },\n fetchPolicy: 'network-only',\n errorPolicy: 'all',\n })\n .then(({ data }) => {\n if (cancelled) return;\n const snapshot = data?.messengerActiveStream as\n | { runId?: string; text?: string; isFinal?: boolean }\n | null\n | undefined;\n if (!snapshot?.text || snapshot.isFinal) return;\n if (streamCompletedRef.current) return;\n if (accumulatedLenRef.current > 0) return;\n reconnectedStreamRef.current = true;\n hasReceivedDeltasRef.current = true;\n if (snapshot.runId) lastRunIdRef.current = snapshot.runId;\n ingestAccumulatedText(snapshot.text);\n })\n .catch(() => {\n /* pre-#701 backend or transient failure — live deltas self-heal */\n });\n return () => {\n cancelled = true;\n };\n }, [streamChannelId, streamSkip, apolloClient, ingestAccumulatedText]);\n\n useGatewayInboundMessageByChannelSubscription({\n variables: { channelId: streamChannelId },\n skip: streamSkip,\n onData: ({ data }) => {\n const msg = data?.data?.gatewayInboundMessageByChannel;\n if (!msg) return;\n if (streamCompletedRef.current) return;\n const sanitizedText = stripModelCostHeader(msg.text ?? '');\n const { preResponse, text: finalText } = splitPreResponse(sanitizedText);\n if (preResponse) cbRef.current.onQuickReply?.(preResponse);\n // Fast-lane-only inbound: keep waiting for the agent complete.\n if (msg.text && !finalText.trim() && preResponse) return;\n if (!finalText.trim() && !pendingRef.current && !hasReceivedDeltasRef.current) return;\n\n if (finalText.trim() && !hasReceivedDeltasRef.current) {\n cbRef.current.onChunk(finalText);\n }\n\n cbRef.current.onComplete(resolveAssistantReply(finalText));\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = true;\n accumulatedLenRef.current = 0;\n lastRunIdRef.current = null;\n runStartedAtRef.current = null;\n clearRunTimers();\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 (\n text: string,\n chatId = 'messenger',\n media: Array<{ type: string; url: string; data?: string; mimeType?: string; filename?: string }> = [],\n ): Promise<boolean> => {\n if (!isConnected) return false;\n\n pendingRef.current = { text, sentAt: Date.now(), chatId };\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = false;\n accumulatedLenRef.current = 0;\n lastRunIdRef.current = null;\n quickReplyShownRef.current = false;\n\n // Ask the brain the same question in parallel with the send. On a cold\n // first turn the agent can take minutes before its first delta; the\n // brain answers in seconds. Its reply is painted only if nothing real\n // has arrived yet, and is replaced in place the moment it does. Fire\n // and forget: a brain failure changes nothing about the turn.\n // Never run it for media turns — the brain only sees `text`, so\n // \"what is this\" + a photo becomes a generic \"what are you referring to\".\n const turn = ++turnSeqRef.current;\n if (media.length === 0) {\n void quickReplyViaBrain(text).then((answer) => {\n if (!answer) return;\n // Stale if the turn moved on, or the agent already spoke.\n if (turnSeqRef.current !== turn || !pendingRef.current) return;\n if (hasReceivedDeltasRef.current || streamCompletedRef.current) return;\n quickReplyShownRef.current = true;\n cbRef.current.onQuickReply?.(answer);\n });\n }\n\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text,\n ...(media.length > 0 ? { media: media as never } : {}),\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 runStartedAtRef.current = null;\n clearRunTimers();\n return false;\n }\n\n runStartedAtRef.current = Date.now();\n armTimeout();\n armMaxRun();\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 runStartedAtRef.current = null;\n clearRunTimers();\n return false;\n }\n },\n [isConnected, accountId, channelId, sendMutation, model, skill, armTimeout, armMaxRun, clearRunTimers],\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 lastRunIdRef.current = null;\n runStartedAtRef.current = null;\n clearRunTimers();\n },\n [clearRunTimers],\n );\n\n /**\n * Inject a follow-up into the turn that is currently streaming.\n * Same `gatewaySendMessage` mutation with `metadata.steer = true` (browser\n * `useCdecliChannel.steer`). Do NOT reset streaming refs: the existing\n * delta subscription must keep running.\n *\n * \"Cannot steer: no active session\" is an expected race before cdecli has\n * started the turn. Callers should queue and retry; do not `console.error`\n * (that pops the RN LogBox and looks like a crash).\n */\n const steer = useCallback(\n async (text: string, chatId = 'messenger'): Promise<SteerResult> => {\n if (!isConnected) return { ok: false, error: 'CDeCLI is not connected.' };\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text,\n metadata: { steer: true },\n },\n },\n });\n const payload = result.data?.gatewaySendMessage;\n if (!payload?.success) {\n const errMsg = payload?.error || 'CDeCLI steer failed';\n if (!isNoActiveSessionError(errMsg)) {\n console.warn('[useCdecliChannel] steer failed:', errMsg);\n }\n return { ok: false, error: errMsg };\n }\n return { ok: true };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn('[useCdecliChannel] steer mutation error:', msg);\n return { ok: false, error: msg };\n }\n },\n [isConnected, accountId, sendMutation],\n );\n\n /**\n * Abort the in-flight turn. `metadata.cancel = true` maps to\n * `POST /v1/chat/cancel` on cdecli-serve (browser parity).\n */\n const cancel = useCallback(\n async (chatId = 'messenger'): Promise<boolean> => {\n if (!isConnected) return false;\n pendingRef.current = null;\n runStartedAtRef.current = null;\n clearRunTimers();\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text: '',\n metadata: { cancel: true },\n },\n },\n });\n const payload = result.data?.gatewaySendMessage;\n if (!payload?.success) {\n const errMsg = payload?.error || 'CDeCLI cancel failed';\n // Trash-clear / greet flows cancel with nothing running — expected.\n if (!isNoActiveSessionError(errMsg)) {\n console.warn('[useCdecliChannel] cancel failed:', errMsg);\n }\n return false;\n }\n return true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[useCdecliChannel] cancel mutation error:', msg);\n return false;\n }\n },\n [isConnected, accountId, sendMutation, clearRunTimers],\n );\n\n return { sendMessage, steer, cancel };\n}\n"],"names":["_a"],"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, chatId, media)` 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 { AppState } from 'react-native';\nimport { useApolloClient } from '@apollo/client';\nimport { isConnectorReply } from 'common';\nimport { quickReplyViaBrain } from '../services/brainQuickReply';\nimport { MESSENGER_ACTIVE_STREAM_QUERY, stripModelCostHeader } from './messengerStream';\nimport {\n CHAT_RUN_TIMEOUT_TEXT,\n CDECLI_MAX_RUN_MS,\n EMPTY_CHAT_RESPONSE_TEXT,\n resolveAssistantReply,\n} from '../features/chat/emptyChatResponse';\nimport { isFastLaneRunId, splitPreResponse } from '../features/chat/preResponse';\nimport {\n useGatewaySendMessageMutation,\n useGatewayInboundMessageByChannelSubscription,\n useMessengerStreamDeltaSubscription,\n} from 'common/graphql';\n\n// `error?: undefined` on the success arm keeps `result.error` reachable on the\n// union: with strictNullChecks off (repo-wide), TS will not narrow the\n// discriminated union after an `if (result.ok) continue`.\nexport type SteerResult = { ok: true; error?: undefined } | { ok: false; error: string };\n\nexport interface CdecliChannelCallbacks {\n onChunk: (text: string) => void;\n /**\n * A fast provisional answer from `yantra-brain` or `CDECLI_FAST_LANE=pre`.\n * Shown as a Quick answer strip — never the main bubble. Pass the full\n * text so far (not a delta). Do not pass `''` to hide it when the agent\n * starts; web keeps the strip beside the agent reply.\n */\n onQuickReply?: (text: string) => void;\n /**\n * The agent started a new run (typically after the fast lane). Clear the\n * main bubble so the agent's tokens do not append onto the provisional\n * article.\n */\n onStreamRestart?: () => void;\n onComplete: (text: string) => void;\n onError: (error: string) => void;\n}\n\nexport function isNoActiveSessionError(message: string): boolean {\n return /no active session/i.test(message);\n}\n\n/**\n * Idle window, not a total budget. The timer is re-armed on every streamed\n * delta (a turn that is actively streaming is not stuck), and paused while the\n * app is backgrounded (iOS suspends the WebSocket + throttles JS timers, so a\n * wall-clock timer would otherwise fire a false \"did not respond\" the instant\n * the app returns to the foreground). It fires only after this much CONTINUOUS\n * silence with the app in the foreground.\n */\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; chatId: string } | null>(null);\n const hasReceivedDeltasRef = useRef(false);\n const reconnectedStreamRef = useRef(false);\n const streamCompletedRef = useRef(false);\n const accumulatedLenRef = useRef(0);\n const lastRunIdRef = useRef<string | null>(null);\n const apolloClient = useApolloClient();\n const seededChannelRef = useRef<string | null>(null);\n const quickReplyShownRef = useRef(false);\n\n /**\n * Forward newly-accumulated stream text. Shared by live deltas and the\n * mount-time resume seed so neither gaps nor double-delivery happen when\n * the user leaves Chat for history and comes back mid-turn.\n */\n const ingestAccumulatedText = useCallback((accumulated: string, asFastLane = false) => {\n const stripped = stripModelCostHeader(accumulated);\n const { preResponse, text: fullText } = splitPreResponse(stripped);\n if (preResponse) {\n quickReplyShownRef.current = true;\n cbRef.current.onQuickReply?.(preResponse);\n }\n if (asFastLane) {\n const fast = (preResponse || fullText).trim();\n if (fast) {\n quickReplyShownRef.current = true;\n cbRef.current.onQuickReply?.(fast);\n }\n return;\n }\n const newChunk = fullText.substring(accumulatedLenRef.current);\n accumulatedLenRef.current = fullText.length;\n if (newChunk) cbRef.current.onChunk(newChunk);\n }, []);\n // Identity of the current turn for the brain race. A counter, not a\n // timestamp: two sends can share a millisecond, and a timestamp would then\n // let a brain answer for a superseded turn paint over the live one.\n const turnSeqRef = useRef(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const maxRunTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const runStartedAtRef = useRef<number | null>(null);\n const stoppingRef = useRef(false);\n\n const streamSkip = !channelId;\n const streamChannelId = channelId || accountId;\n\n const clearRunTimers = useCallback(() => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n if (maxRunTimerRef.current) {\n clearTimeout(maxRunTimerRef.current);\n maxRunTimerRef.current = null;\n }\n }, []);\n\n const stopTurn = useCallback(\n (reason: 'idle' | 'max-run') => {\n const pending = pendingRef.current;\n if (!pending || stoppingRef.current) return;\n stoppingRef.current = true;\n const chatId = pending.chatId;\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n streamCompletedRef.current = true;\n runStartedAtRef.current = null;\n clearRunTimers();\n void sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text: '',\n metadata: { cancel: true },\n },\n },\n }).catch(() => {\n /* local stop still lands a standard reply */\n });\n cbRef.current.onComplete(reason === 'max-run' ? CHAT_RUN_TIMEOUT_TEXT : EMPTY_CHAT_RESPONSE_TEXT);\n stoppingRef.current = false;\n },\n [accountId, clearRunTimers, sendMutation],\n );\n\n /** (Re)start the idle timer for the in-flight turn. No-op if none pending. */\n const armTimeout = useCallback(() => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n if (!pendingRef.current) {\n timeoutRef.current = null;\n return;\n }\n timeoutRef.current = setTimeout(() => stopTurn('idle'), CDECLI_RESPONSE_TIMEOUT_MS);\n }, [stopTurn]);\n\n /** Hard 2-minute cap from send start — still-streaming turns are cancelled too. */\n const armMaxRun = useCallback(() => {\n if (maxRunTimerRef.current) clearTimeout(maxRunTimerRef.current);\n const started = runStartedAtRef.current;\n if (!pendingRef.current || started == null) {\n maxRunTimerRef.current = null;\n return;\n }\n const remaining = CDECLI_MAX_RUN_MS - (Date.now() - started);\n maxRunTimerRef.current = setTimeout(() => stopTurn('max-run'), Math.max(0, remaining));\n }, [stopTurn]);\n\n // Pause JS timers in the background; on resume re-arm from wall-clock start\n // so a 2-minute run that finished while away is stopped immediately.\n useEffect(() => {\n const sub = AppState.addEventListener('change', (next) => {\n if (next === 'active') {\n if (pendingRef.current && !streamCompletedRef.current) {\n armTimeout();\n armMaxRun();\n }\n } else {\n clearRunTimers();\n }\n });\n return () => sub.remove();\n }, [armMaxRun, armTimeout, clearRunTimers]);\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 const prevRunId = lastRunIdRef.current;\n if (delta.runId && delta.runId !== prevRunId) {\n accumulatedLenRef.current = 0;\n // Fast-lane run (`fast-…`) then agent (`serve-…`): start a fresh\n // bubble instead of appending the agent onto the provisional article.\n if (prevRunId && !isFastLaneRunId(delta.runId)) {\n cbRef.current.onStreamRestart?.();\n }\n }\n if (delta.runId) lastRunIdRef.current = delta.runId;\n\n if (streamCompletedRef.current) return;\n\n // Pick up a turn that kept running after the user left Chat for history.\n if (!pendingRef.current) {\n if (!reconnectedStreamRef.current) {\n reconnectedStreamRef.current = true;\n accumulatedLenRef.current = 0;\n }\n }\n\n hasReceivedDeltasRef.current = true;\n armTimeout();\n ingestAccumulatedText(delta.text ?? '', isFastLaneRunId(delta.runId));\n },\n onError: (err) => {\n console.error('[useCdecliChannel] stream delta subscription error:', err);\n },\n });\n\n // Seed the in-flight reply when Chat remounts (history → chat) mid-stream.\n useEffect(() => {\n if (streamSkip || !streamChannelId) return undefined;\n if (seededChannelRef.current === streamChannelId) return undefined;\n seededChannelRef.current = streamChannelId;\n accumulatedLenRef.current = 0;\n streamCompletedRef.current = false;\n reconnectedStreamRef.current = false;\n lastRunIdRef.current = null;\n\n let cancelled = false;\n apolloClient\n .query({\n query: MESSENGER_ACTIVE_STREAM_QUERY,\n variables: { channelId: streamChannelId },\n fetchPolicy: 'network-only',\n errorPolicy: 'all',\n })\n .then(({ data }) => {\n if (cancelled) return;\n const snapshot = data?.messengerActiveStream as\n | { runId?: string; text?: string; isFinal?: boolean }\n | null\n | undefined;\n if (!snapshot?.text || snapshot.isFinal) return;\n if (streamCompletedRef.current) return;\n if (accumulatedLenRef.current > 0) return;\n reconnectedStreamRef.current = true;\n hasReceivedDeltasRef.current = true;\n if (snapshot.runId) lastRunIdRef.current = snapshot.runId;\n ingestAccumulatedText(snapshot.text);\n })\n .catch(() => {\n /* pre-#701 backend or transient failure — live deltas self-heal */\n });\n return () => {\n cancelled = true;\n };\n }, [streamChannelId, streamSkip, apolloClient, ingestAccumulatedText]);\n\n useGatewayInboundMessageByChannelSubscription({\n variables: { channelId: streamChannelId },\n skip: streamSkip,\n onData: ({ data }) => {\n const msg = data?.data?.gatewayInboundMessageByChannel;\n if (!msg) return;\n if (streamCompletedRef.current) return;\n const sanitizedText = stripModelCostHeader(msg.text ?? '');\n const { preResponse, text: finalText } = splitPreResponse(sanitizedText);\n if (preResponse) cbRef.current.onQuickReply?.(preResponse);\n // Fast-lane-only inbound: keep waiting for the agent complete.\n if (msg.text && !finalText.trim() && preResponse) return;\n if (!finalText.trim() && !pendingRef.current && !hasReceivedDeltasRef.current) return;\n\n if (finalText.trim() && !hasReceivedDeltasRef.current) {\n cbRef.current.onChunk(finalText);\n }\n\n cbRef.current.onComplete(resolveAssistantReply(finalText));\n pendingRef.current = null;\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = true;\n accumulatedLenRef.current = 0;\n lastRunIdRef.current = null;\n runStartedAtRef.current = null;\n clearRunTimers();\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 (\n text: string,\n chatId = 'messenger',\n media: Array<{ type: string; url: string; data?: string; mimeType?: string; filename?: string }> = [],\n ): Promise<boolean> => {\n if (!isConnected) return false;\n\n pendingRef.current = { text, sentAt: Date.now(), chatId };\n hasReceivedDeltasRef.current = false;\n reconnectedStreamRef.current = false;\n streamCompletedRef.current = false;\n accumulatedLenRef.current = 0;\n lastRunIdRef.current = null;\n quickReplyShownRef.current = false;\n\n // Ask the brain the same question in parallel with the send. On a cold\n // first turn the agent can take minutes before its first delta; the\n // brain answers in seconds. Its reply is painted only if nothing real\n // has arrived yet, and is replaced in place the moment it does. Fire\n // and forget: a brain failure changes nothing about the turn.\n // Never run it for media turns — the brain only sees `text`, so\n // \"what is this\" + a photo becomes a generic \"what are you referring to\".\n // Nor for a connect card's `[CONNECTOR_CONNECTED:…]` / `[CONNECTOR_DECLINED:…]`\n // reply: it answers the agent's parked request and asks nothing, so a brain\n // \"answer\" would paint a bogus bubble over the agent's real follow-up.\n const turn = ++turnSeqRef.current;\n if (media.length === 0 && !isConnectorReply(text)) {\n void quickReplyViaBrain(text).then((answer) => {\n if (!answer) return;\n // Stale if the turn moved on, or the agent already spoke.\n if (turnSeqRef.current !== turn || !pendingRef.current) return;\n if (hasReceivedDeltasRef.current || streamCompletedRef.current) return;\n quickReplyShownRef.current = true;\n cbRef.current.onQuickReply?.(answer);\n });\n }\n\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text,\n ...(media.length > 0 ? { media: media as never } : {}),\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 runStartedAtRef.current = null;\n clearRunTimers();\n return false;\n }\n\n runStartedAtRef.current = Date.now();\n armTimeout();\n armMaxRun();\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 runStartedAtRef.current = null;\n clearRunTimers();\n return false;\n }\n },\n [isConnected, accountId, channelId, sendMutation, model, skill, armTimeout, armMaxRun, clearRunTimers],\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 lastRunIdRef.current = null;\n runStartedAtRef.current = null;\n clearRunTimers();\n },\n [clearRunTimers],\n );\n\n /**\n * Inject a follow-up into the turn that is currently streaming.\n * Same `gatewaySendMessage` mutation with `metadata.steer = true` (browser\n * `useCdecliChannel.steer`). Do NOT reset streaming refs: the existing\n * delta subscription must keep running.\n *\n * \"Cannot steer: no active session\" is an expected race before cdecli has\n * started the turn. Callers should queue and retry; do not `console.error`\n * (that pops the RN LogBox and looks like a crash).\n */\n const steer = useCallback(\n async (text: string, chatId = 'messenger'): Promise<SteerResult> => {\n if (!isConnected) return { ok: false, error: 'CDeCLI is not connected.' };\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text,\n metadata: { steer: true },\n },\n },\n });\n const payload = result.data?.gatewaySendMessage;\n if (!payload?.success) {\n const errMsg = payload?.error || 'CDeCLI steer failed';\n if (!isNoActiveSessionError(errMsg)) {\n console.warn('[useCdecliChannel] steer failed:', errMsg);\n }\n return { ok: false, error: errMsg };\n }\n return { ok: true };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn('[useCdecliChannel] steer mutation error:', msg);\n return { ok: false, error: msg };\n }\n },\n [isConnected, accountId, sendMutation],\n );\n\n /**\n * Abort the in-flight turn. `metadata.cancel = true` maps to\n * `POST /v1/chat/cancel` on cdecli-serve (browser parity).\n */\n const cancel = useCallback(\n async (chatId = 'messenger'): Promise<boolean> => {\n if (!isConnected) return false;\n pendingRef.current = null;\n runStartedAtRef.current = null;\n clearRunTimers();\n try {\n const result = await sendMutation({\n variables: {\n input: {\n channelType: 'cdecli-serve',\n accountId,\n chatId,\n text: '',\n metadata: { cancel: true },\n },\n },\n });\n const payload = result.data?.gatewaySendMessage;\n if (!payload?.success) {\n const errMsg = payload?.error || 'CDeCLI cancel failed';\n // Trash-clear / greet flows cancel with nothing running — expected.\n if (!isNoActiveSessionError(errMsg)) {\n console.warn('[useCdecliChannel] cancel failed:', errMsg);\n }\n return false;\n }\n return true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[useCdecliChannel] cancel mutation error:', msg);\n return false;\n }\n },\n [isConnected, accountId, sendMutation, clearRunTimers],\n );\n\n return { sendMessage, steer, cancel };\n}\n"],"names":["_a"],"mappings":"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"}
|
package/lib/hooks/useChatApi.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {useApolloClient}from'@apollo/client/index.js';import {SortEnum,RoomType,PostTypeEnum,AiAgentMessageRole}from'common';import {useGetChannelsByUserWithLastMessageQuery,useMessagesQuery,MessagesDocument,useAddChannelMutation,useSendMessagesMutation,OnChatMessageAddedDocument,GetChannelsByUserWithLastMessageDocument}from'common/graphql';import {useMemo,useCallback,useEffect}from'react';import {v4}from'uuid';import {isAttachedCaption,historyAttachmentTitle,attachmentsFromUserContent}from'../features/attachments/historyAttachmentLabel.js';import {stripAskUser}from'../features/chat/askUser.js';import {parseToolActivity}from'../features/chat/toolActivity.js';var __defProp = Object.defineProperty;
|
|
1
|
+
import {useApolloClient}from'@apollo/client/index.js';import {SortEnum,RoomType,PostTypeEnum,isConnectorReply,stripConnectorConnect,AiAgentMessageRole}from'common';import {useGetChannelsByUserWithLastMessageQuery,useMessagesQuery,MessagesDocument,useAddChannelMutation,useSendMessagesMutation,OnChatMessageAddedDocument,GetChannelsByUserWithLastMessageDocument}from'common/graphql';import {useMemo,useCallback,useEffect}from'react';import {v4}from'uuid';import {isAttachedCaption,historyAttachmentTitle,attachmentsFromUserContent}from'../features/attachments/historyAttachmentLabel.js';import {stripAskUser}from'../features/chat/askUser.js';import {parseToolActivity}from'../features/chat/toolActivity.js';var __defProp = Object.defineProperty;
|
|
2
2
|
var __defProps = Object.defineProperties;
|
|
3
3
|
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
4
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
@@ -280,6 +280,7 @@ function cleanMessageText(raw) {
|
|
|
280
280
|
return raw.replace(ACTIVE_CONNECTORS_PREFIX_RE, "").replace(/\s+/g, " ").trim();
|
|
281
281
|
}
|
|
282
282
|
function isTransientHistoryText(text) {
|
|
283
|
+
if (isConnectorReply(text)) return true;
|
|
283
284
|
const t = cleanHistoryPreview(text) || cleanMessageText(text);
|
|
284
285
|
if (!t) return true;
|
|
285
286
|
if (/^thinking[.…]*$/i.test(t)) return true;
|
|
@@ -297,7 +298,8 @@ function looksLikeAssistantReply(text, role) {
|
|
|
297
298
|
return false;
|
|
298
299
|
}
|
|
299
300
|
function cleanHistoryPreview(raw) {
|
|
300
|
-
|
|
301
|
+
if (isConnectorReply(raw)) return "";
|
|
302
|
+
const withoutAsk = stripConnectorConnect(stripAskUser(raw));
|
|
301
303
|
const {
|
|
302
304
|
text: withoutTools
|
|
303
305
|
} = parseToolActivity(withoutAsk, false);
|