@ethisyscore/plugin-ui 1.131.1 → 1.133.0
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/dist/components/connectors/index.cjs +46 -7
- package/dist/components/connectors/index.cjs.map +1 -1
- package/dist/components/connectors/index.d.cts +2 -2
- package/dist/components/connectors/index.d.ts +2 -2
- package/dist/components/connectors/index.js +46 -7
- package/dist/components/connectors/index.js.map +1 -1
- package/dist/connector-mappings/index.cjs +12 -3
- package/dist/connector-mappings/index.cjs.map +1 -1
- package/dist/connector-mappings/index.d.cts +8 -2
- package/dist/connector-mappings/index.d.ts +8 -2
- package/dist/connector-mappings/index.js +12 -3
- package/dist/connector-mappings/index.js.map +1 -1
- package/dist/{mappingQueryKeys-DsZJn1sk.d.cts → mappingQueryKeys-_hEc0heh.d.cts} +12 -1
- package/dist/{mappingQueryKeys-DsZJn1sk.d.ts → mappingQueryKeys-_hEc0heh.d.ts} +12 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/connector-mappings/ConnectorMappingService.ts","../../src/components/connectors/mappingQueryKeys.ts","../../src/components/connectors/mappingCompleteness.ts","../../src/platform-react/useAuthenticatedQuery.ts","../../src/connector-mappings/useConnectorMappings.ts","../../src/components/shared/toast.ts","../../src/connector-mappings/types.ts","../../src/connector-mappings/useConnectorMappingMutations.ts","../../src/connector-mappings/useMappingAdvisors.ts","../../src/connector-mappings/useMappingTranslationEditor.ts","../../src/connector-mappings/useAutoMatchOnConnectorSwitch.ts","../../src/connector-mappings/useMappingBannerDismissal.ts","../../src/connector-mappings/useModuleProvider.ts","../../src/connector-mappings/targetSelection.ts","../../src/connector-mappings/useConnectorToolMappings.ts","../../src/connector-mappings/connectorOptions.ts","../../src/components/connectors/providerLabels.ts"],"names":["showSuccessToast","showWarningToast","useMutation","useState","useRef","useEffect","useCallback","useQueryClient","useMemo"],"mappings":"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","file":"index.js","sourcesContent":["import type { ToolInvoker } from \"../platform-react/mcpService\";\nimport type {\n IConnectorEnumerationService,\n IConnectorMappingService,\n} from \"./IConnectorMappingService\";\nimport type {\n AutoMatchResponse,\n ConnectorMappingItem,\n ConnectorMappingSuggestionResult,\n ConnectorSummary,\n ConnectorToolSummary,\n MappingCompletenessResult,\n ModuleProvider,\n ModuleProviderKind,\n PluginProviderCandidate,\n PluginTargetMappingItem,\n SetPluginTargetMappingArgs,\n SetPluginTargetMappingResult,\n SuggestMappingTranslationBody,\n TestMappingBody,\n TestToolMappingResult,\n} from \"./types\";\n\n/** Response envelope for `connectors:mappings:list`. */\ninterface ListMappingsResponse {\n items?: ConnectorMappingItem[];\n}\n\n/** Response for `connectors:mappings:set`: the replacement concurrency token. */\ninterface SetMappingResponse {\n rowVersion?: string | null;\n findings?: MappingVerificationFinding[] | null;\n}\n\n/**\n * What the save-time verification gate could establish about the mapping that was stored.\n *\n * Rides on a SUCCESS response: the gate refuses only what the invocation path refuses on every\n * call, and everything else it establishes is true and worth an operator seeing without standing\n * in the way of a save.\n */\nexport interface MappingVerificationFinding {\n /** Stable code, e.g. `CON_MAP_020`. */\n code: string;\n /** `Warning`, or `Refusal` for one the caller acknowledged. */\n severity: string;\n /** Short operator-facing summary. */\n message: string;\n /** The longer explanation, when there is one. */\n detail?: string | null;\n}\n\n/** What a mapping save returns: the refreshed token, and anything the gate had to say. */\nexport interface MappingSaveOutcome {\n rowVersion: string | null;\n findings: MappingVerificationFinding[];\n}\n\n/**\n * Refuses a plugin-target call the host cannot currently serve, naming the layer below.\n *\n * The message carries the missing transport rather than a generic failure, because the layer that\n * knows WHY is this one and a caller that receives \"request failed\" has to guess. `detail` names\n * what exists and what does not, so the fix is legible from the message without reading this file.\n */\nfunction pluginTargetSurfaceUnavailable(method: string, detail: string): never {\n throw new Error(\n `ConnectorMappingService.${method} is not available. ${detail} ` +\n \"Subclass this service with your own implementation, or leave the plugin-target surface \" +\n \"unmounted (isPluginTargetSurfaceAvailable is false on this implementation).\",\n );\n}\n\n/**\n * The connector tool-mapping surface, served entirely by the host's `connectors:mappings:*` tools.\n *\n * ## Why the host store and not a plugin table\n *\n * A plugin resolves its operations at runtime through the platform's `resolve-mapping`, so the rows\n * have to live in the platform store. Three plugins each owned their mappings in their own schema\n * first, and every platform-side tool was blind to them: Super Admin could not inspect or repair\n * them, and platform mapping completeness did not cover them.\n *\n * ## Transport is injected\n *\n * The constructor takes a {@link ToolInvoker} — the same shape `BaseMcpService` takes — rather than\n * importing one. That keeps this class free of `@ethisyscore/extension-runtime`, so it is unit\n * testable with a plain function, and it is why this does not extend `BaseMcpService`: the base\n * class exists to give a lifted REST service a `this.tool(...)`, and there is no REST ancestry here\n * to accommodate. A plugin constructs it exactly as before:\n *\n * ```ts\n * const invoker = useToolInvokerMap(CONNECTOR_MAPPING_TOOLS);\n * const service = useMemo(() => new ConnectorMappingService(invoker), [invoker]);\n * ```\n *\n * ## Row versions are opaque\n *\n * The host declares them as `byte[]`, which crosses the wire as base64. Read one from\n * {@link getMappings} and hand the same string back on write; nothing on this side parses or\n * constructs one.\n *\n * ## Translation is NOT a patch on this surface\n *\n * The host writes NULL for a null member, so every save must carry the complete translation.\n * Sending only the field being changed silently clears the other two — that defect reached\n * production and had to be fixed three times, once per plugin, which is the reason this class is\n * here. `useConnectorMappingMutations` is where completeness is enforced; this class sends what it\n * is given, and does not paper over a partial write.\n *\n * ## Every read is null-tolerant\n *\n * Promoted from the three copies, taking the most hardened behaviour of each. A `Result` with no\n * value is representable on the wire, so `response.items` on an absent payload throws inside a\n * react-query `queryFn` or, worse, inside an `onSuccess` where the rejection is unhandled. A page\n * that reports nothing beats a page that crashed.\n */\nexport class ConnectorMappingService\n implements IConnectorMappingService, IConnectorEnumerationService\n{\n constructor(private readonly invoker: ToolInvoker) {}\n\n /** Single seam every method goes through, so a subclass can wrap logging or retries in one place. */\n protected async tool<TRes>(name: string, args: unknown = {}): Promise<TRes> {\n return (await this.invoker(name, args)) as TRes;\n }\n\n async getMappings(connectorId: string, moduleKey: string): Promise<ConnectorMappingItem[]> {\n // The `{ items }` envelope is destructured explicitly rather than assumed to be the array.\n // Reading a bare array off it is a defect that actually shipped: the table rendered its column\n // headers with no rows and raised no error.\n const response = await this.tool<ListMappingsResponse | null>(\"connectors:mappings:list\", {\n connectorId,\n moduleKey,\n });\n\n return response?.items ?? [];\n }\n\n async updateMapping(\n connectorId: string,\n moduleKey: string,\n operationKey: string,\n toolName: string,\n rowVersion: string,\n // REQUIRED, and nullable rather than optional. A write here replaces the whole translation, so\n // an omitted argument is indistinguishable from a deliberate clear — which is precisely the\n // defect this package exists to fix, reachable by calling the exported service directly and\n // bypassing the resolver in `useConnectorMappingMutations`. Making the caller pass `null` costs\n // one word and makes \"I am clearing this\" a thing they had to say.\n requestFieldMap: Record<string, string> | null,\n responseFieldPaths: Record<string, string> | null,\n staticRequestFields: Record<string, string> | null,\n ): Promise<MappingSaveOutcome> {\n const response = await this.tool<SetMappingResponse | null>(\"connectors:mappings:set\", {\n connectorId,\n moduleKey,\n operationKey,\n toolName,\n rowVersion,\n // Coalesced to explicit nulls rather than left `undefined`. The host treats both the same\n // way, but an explicit null states on the wire that the caller MEANT no override, which is\n // what makes a request log readable when a translation goes missing.\n requestFieldMap: requestFieldMap ?? null,\n responseFieldPaths: responseFieldPaths ?? null,\n staticRequestFields: staticRequestFields ?? null,\n });\n\n // Returned, never thrown on. The next edit's concurrency check wants this token, so losing it\n // costs a refetch — but two of the three callers this was promoted from never read the value\n // and every one of them invalidates and refetches after a save regardless. Throwing would turn\n // a host response variation into a failed save for consumers that are unaffected by it.\n //\n // FINDINGS travel with it. They used to be dropped here: the host composed them, the command\n // response carried them, and this line returned the row version alone — so CON_MAP_020 and\n // every warning beside it had never once been seen by an operator on any environment. A host\n // that sends none yields an empty list, which reads the same as a clean save.\n return {\n rowVersion: response?.rowVersion ?? null,\n findings: response?.findings ?? [],\n };\n }\n\n async deleteMapping(\n connectorId: string,\n moduleKey: string,\n operationKey: string,\n rowVersion: string,\n ): Promise<void> {\n // Row version is required by the command but not enforced on its unmap branch: clearing a\n // mapping is safe to retry blindly.\n await this.tool<unknown>(\"connectors:mappings:delete\", {\n connectorId,\n moduleKey,\n operationKey,\n rowVersion,\n });\n }\n\n async autoMatch(connectorId: string, moduleKey: string): Promise<AutoMatchResponse> {\n // The host resolves the catalogue for `moduleKey`, strips the `integrations:{slug}:` prefix\n // from the connector's tools before matching, and stores the raw verb. There is deliberately no\n // slug parameter — see `IConnectorMappingService.autoMatch`.\n const response = await this.tool<Partial<AutoMatchResponse> | null>(\n \"connectors:mappings:auto-match\",\n { connectorId, moduleKey },\n );\n\n return {\n items: response?.items ?? [],\n aiAvailable: response?.aiAvailable ?? false,\n aiUnavailableReason: response?.aiUnavailableReason ?? null,\n unmatchedRequiredCount: response?.unmatchedRequiredCount ?? 0,\n conflictReason: response?.conflictReason ?? null,\n };\n }\n\n async getMappingCompleteness(\n connectorId: string,\n moduleKey: string,\n ): Promise<MappingCompletenessResult> {\n const response = await this.tool<MappingCompletenessResult | null>(\n \"connectors:mappings:completeness\",\n { connectorId, moduleKey },\n );\n\n return response ?? { operations: [] };\n }\n\n async suggestMappingTranslation(\n connectorId: string,\n moduleKey: string,\n body: SuggestMappingTranslationBody,\n ): Promise<ConnectorMappingSuggestionResult> {\n const response = await this.tool<ConnectorMappingSuggestionResult | null>(\n \"connectors:mappings:suggest-translation\",\n {\n connectorId,\n moduleKey,\n operationKey: body.operationKey,\n toolName: body.toolName,\n // The host declares SampleArgs non-nullable; an absent sample is an empty object, not null.\n sampleArgs: body.sampleArgs ?? {},\n },\n );\n\n // An absent payload is reported as an unsuccessful suggestion rather than thrown. The editor\n // renders `success: false` as \"no suggestion available\", which is the truth; an exception would\n // read to the operator as though their mapping were broken.\n return (\n response ?? {\n success: false,\n error: \"The host returned no suggestion.\",\n // Empty objects, not null: the contract types these as `Record<string, string>`, and an\n // empty map is the accurate statement — no rewrites proposed.\n requestFieldMap: {},\n responseFieldPaths: {},\n }\n );\n }\n\n async testMapping(\n connectorId: string,\n moduleKey: string,\n body: TestMappingBody,\n ): Promise<TestToolMappingResult> {\n const response = await this.tool<TestToolMappingResult | null>(\"connectors:mappings:test\", {\n connectorId,\n moduleKey,\n operationKey: body.operationKey,\n toolName: body.toolName,\n requestFieldMap: body.requestFieldMap,\n responseFieldPaths: body.responseFieldPaths,\n staticRequestFields: body.staticRequestFields,\n sampleArgs: body.sampleArgs,\n });\n\n return (\n response ?? {\n success: false,\n error: \"The host returned no result for the mapping test.\",\n rawResponseJson: null,\n extractedFields: [],\n }\n );\n }\n\n // -------------------------------------------------------------------------\n // IConnectorEnumerationService — optional, see the interface's remarks\n // -------------------------------------------------------------------------\n\n async listConnectors(): Promise<ConnectorSummary[]> {\n const response = await this.tool<{ items?: ConnectorSummary[] } | null>(\n \"settings:list-connectors\",\n {},\n );\n\n return (response?.items ?? []).map((c) => ({\n id: c.id,\n name: c.name,\n slug: c.slug ?? null,\n isEnabled: c.isEnabled,\n }));\n }\n\n async getModuleProvider(moduleKey: string): Promise<ModuleProvider> {\n const response = await this.tool<ModuleProvider | null>(\"connectors:mappings:get-provider\", {\n moduleKey,\n });\n\n // Null-tolerant like every other read here, and the absent state is the UNCONFIGURED one\n // rather than an error: a module nobody has chosen a provider for is the normal starting\n // point, not a failure.\n return {\n targetKind: response?.targetKind ?? null,\n connectorId: response?.connectorId ?? null,\n targetExtensionId: response?.targetExtensionId ?? null,\n rowVersion: response?.rowVersion ?? null,\n };\n }\n\n async setModuleProvider(\n moduleKey: string,\n targetKind: ModuleProviderKind,\n // Nullable rather than optional, for the same reason the translation members are: the host\n // stores exactly one of these and refuses a row whose two halves disagree, so which one is\n // absent is a thing the caller states rather than omits.\n connectorId: string | null,\n targetExtensionId: string | null,\n // The token read from `getModuleProvider`, or `null` for a module that has never had a\n // selection. Handed back unchanged — see the class remarks on opaque row versions.\n rowVersion: string | null,\n ): Promise<ModuleProvider> {\n const response = await this.tool<ModuleProvider | null>(\"connectors:mappings:set-provider\", {\n moduleKey,\n targetKind,\n connectorId,\n targetExtensionId,\n rowVersion,\n });\n\n // The refreshed token comes back on the write, so a caller can change the provider twice\n // running without a re-read in between. Falling back to null rather than to the token just\n // SENT is deliberate: a stale token presented as current is how an optimistic-concurrency check\n // gets silently defeated, and a null makes the next write re-read instead.\n return {\n targetKind: response?.targetKind ?? targetKind,\n connectorId: response?.connectorId ?? null,\n targetExtensionId: response?.targetExtensionId ?? null,\n rowVersion: response?.rowVersion ?? null,\n };\n }\n\n async listConnectorTools(connectorId: string): Promise<ConnectorToolSummary[]> {\n const response = await this.tool<{ tools?: ConnectorToolSummary[] } | null>(\n \"settings:get-connector\",\n { id: connectorId },\n );\n\n // Disabled tools are returned, not filtered. Hiding one makes \"the tool I want is missing\"\n // indistinguishable from \"the tool I want is switched off\", which need different fixes. The\n // picker filters at the projection step instead — see `mapConnectorToolsToOptions`.\n return response?.tools ?? [];\n }\n\n // -------------------------------------------------------------------------\n // The PLUGIN-TARGET surface - declared, and deliberately not reachable yet\n //\n // The per-module PROVIDER choice above is reachable: CoreConnect-Api #1329 added\n // `connectors:mappings:get-provider` and `connectors:mappings:set-provider`, so an operator can\n // record that a module is served by an installed app. What they still cannot do from a plugin UI\n // is everything BELOW that choice:\n //\n // - the candidate list has no endpoint in ANY transport, so who is on offer has to be supplied to\n // this package rather than fetched by it;\n // - `connectors:mappings:list` takes a connectorId and its rows carry no TargetKind or\n // TargetExtensionId, so there is no connector-less read for a plugin-served module;\n // - `SetPluginTargetMapping` exists as a REST route only\n // (`PUT /api/v1/connectors/modules/{moduleKey}/operations/{operationKey}/plugin-target`), and a\n // plugin's UI reaches the host over MCP, so the per-operation plugin-target write cannot be\n // called from here.\n //\n // Implemented as refusals rather than omitted so the shape a real transport must satisfy is\n // written down in one place, and so the gap is a message an operator's log can carry rather than\n // a method that silently is not there. `isPluginTargetSurfaceAvailable` is the flag a caller\n // reads; none of the methods below is called while it is false.\n // -------------------------------------------------------------------------\n\n /**\n * Whether this transport can serve the plugin-target methods below. `false` here, and the three\n * methods throw, for the reasons in the block comment above.\n *\n * A property and not a guess: \"the method exists\" and \"the method works\" are different facts, and\n * a caller that probed by calling and catching would turn a known gap into a request, an error\n * toast and a support ticket.\n */\n public readonly isPluginTargetSurfaceAvailable = false;\n\n async listPluginProviders(moduleKey: string): Promise<PluginProviderCandidate[]> {\n void moduleKey;\n pluginTargetSurfaceUnavailable(\n \"listPluginProviders\",\n \"The host exposes no candidate listing in any transport. Its routable-tool listing is \" +\n \"documented as unsafe to show an end user verbatim because it applies no caller-permission \" +\n \"filter, and the write path additionally refuses any tool the consuming application did \" +\n \"not declare in its manifest - so the candidate set is a server-side decision, not a \" +\n \"client-side filter of a broader list.\",\n );\n }\n\n async getPluginTargetMappings(moduleKey: string): Promise<PluginTargetMappingItem[]> {\n void moduleKey;\n pluginTargetSurfaceUnavailable(\n \"getPluginTargetMappings\",\n \"The host's mapping list read is scoped BY connector (connectors:mappings:list takes a \" +\n \"connectorId) and its rows carry no TargetKind or TargetExtensionId, so a plugin-target \" +\n \"row has neither a connector to scope it by nor a way to be recognised in the result. No \" +\n \"connector-less list exists.\",\n );\n }\n\n async setPluginTargetMapping(\n moduleKey: string,\n args: SetPluginTargetMappingArgs,\n ): Promise<SetPluginTargetMappingResult> {\n void moduleKey;\n void args;\n pluginTargetSurfaceUnavailable(\n \"setPluginTargetMapping\",\n \"The host serves PUT /api/v1/connectors/modules/{moduleKey}/operations/{operationKey}\" +\n \"/plugin-target over REST only; there is no connectors:mappings:* tool for it, and a \" +\n \"plugin's UI reaches the host over MCP.\",\n );\n }\n}\n\n/** Every host tool this service dispatches, for `useToolInvokerMap`. Frozen and constant-length. */\nexport const CONNECTOR_MAPPING_TOOLS = [\n \"connectors:mappings:list\",\n \"connectors:mappings:set\",\n \"connectors:mappings:delete\",\n \"connectors:mappings:auto-match\",\n \"connectors:mappings:completeness\",\n \"connectors:mappings:suggest-translation\",\n \"connectors:mappings:test\",\n \"connectors:mappings:get-provider\",\n \"connectors:mappings:set-provider\",\n] as const;\n\n/**\n * The two enumeration tools, listed separately so a plugin that has its own connector service does\n * not have to declare invokers for tools it never calls. Spread both when using the default\n * enumeration implementation.\n */\nexport const CONNECTOR_ENUMERATION_TOOLS = [\n \"settings:list-connectors\",\n \"settings:get-connector\",\n] as const;\n","/**\n * React-query keys for a connector's tool mappings and the completeness read derived from them.\n *\n * @remarks\n * The completeness key is built as a CHILD of the mappings key, and that nesting is the whole point\n * of shipping these from the SDK rather than letting each plugin declare its own.\n *\n * Completeness is derived state: it is the mappings joined against the live tool schemas. Every write\n * that changes a mapping can change it. When the two keys are siblings, a mutation has to remember to\n * invalidate both, and one plugin duly invalidated only the mappings key — so its \"operations aren't\n * fully mapped\" banner went stale after every save, clear and auto-match, and stayed stale until the\n * operator navigated away. Nesting means a single `invalidateQueries({ queryKey: mappings })` takes\n * the completeness read with it, and there is nothing left to forget.\n *\n * @example\n * ```ts\n * const mappings = connectorMappingsQueryKey(connectorId, moduleKey);\n *\n * useQuery({ queryKey: mappings, queryFn: ... });\n * useQuery({ queryKey: connectorMappingCompletenessQueryKey(connectorId, moduleKey), queryFn: ... });\n *\n * // One call. Covers both, and any future query keyed beneath the mappings key.\n * queryClient.invalidateQueries({ queryKey: mappings });\n * ```\n */\n\n/** Root segment for every connector-mapping query, so one prefix can invalidate the family. */\nconst CONNECTOR_MAPPINGS_ROOT = \"connector-mappings\" as const;\n\n/**\n * Key for the mappings list of one connector + module.\n *\n * `connectorId` is accepted as possibly-undefined because the page renders before a connector is\n * chosen; pair it with `enabled: !!connectorId` rather than branching on the key.\n */\nexport function connectorMappingsQueryKey(\n connectorId: string | undefined,\n moduleKey: string,\n) {\n return [CONNECTOR_MAPPINGS_ROOT, connectorId, moduleKey] as const;\n}\n\n/**\n * The whole family's prefix, for the one invalidation that has to cross connectors.\n *\n * Almost every write is scoped to one connector and should invalidate only that connector's subtree\n * via {@link connectorMappingsQueryKey}. Changing a module's PROVIDER is the exception: it moves the\n * module from one provider's rows to another's, so the subtree that must be discarded is the one\n * being left, which the new scope's key does not name.\n */\nexport function connectorMappingsRootQueryKey() {\n return [CONNECTOR_MAPPINGS_ROOT] as const;\n}\n\n// The provider selection's own key lives with the hook that owns it, in\n// `connector-mappings/useModuleProvider` - `moduleProviderQueryKey` there. It is inside this family\n// (`[\"connector-mappings\", \"module-provider\", moduleKey]`) so the root invalidation above covers it,\n// but NOT beneath a connector id: the selection is what CHOOSES the connector, so nesting it under\n// one would move the cache entry every time the operator switched and lose it at the single moment\n// it has to be correct.\n\n/**\n * Key for the mapping rows of a module served by an installed application.\n *\n * A sibling of {@link connectorMappingsQueryKey} rather than a variant of it, because the read\n * behind it is a different one: the connector list read is scoped by connector id and a\n * plugin-target row has none. Keyed under the same root so the family invalidation covers it.\n */\nexport function pluginTargetMappingsQueryKey(moduleKey: string) {\n return [CONNECTOR_MAPPINGS_ROOT, \"plugin-target\", moduleKey] as const;\n}\n\n/**\n * Key for the completeness read, nested UNDER {@link connectorMappingsQueryKey} so invalidating the\n * mappings prefix always covers it. Do not flatten this into a sibling key.\n */\nexport function connectorMappingCompletenessQueryKey(\n connectorId: string | undefined,\n moduleKey: string,\n) {\n return [...connectorMappingsQueryKey(connectorId, moduleKey), \"completeness\"] as const;\n}\n","import type { MappingCompletenessItem } from \"./types\";\n\n/**\n * An operation is INCOMPLETE when it has at least one required canonical field that does not map\n * onto the live tool's schema. Deliberately not \"has no tool\": see {@link MappingCompletenessItem}.\n */\nfunction hasUnmappedRequiredField(op: MappingCompletenessItem): boolean {\n return op.unmappedRequiredCanonicalFields.length > 0;\n}\n\n/**\n * Every operation with a gap, required or optional. Use it to decide whether to mount a banner at\n * all, without duplicating the predicate.\n */\nexport function selectIncompleteOperations(\n operations: MappingCompletenessItem[],\n): MappingCompletenessItem[] {\n return operations.filter(hasUnmappedRequiredField);\n}\n\n/**\n * REQUIRED operations with a gap. These genuinely block the module: the connector cannot run them.\n */\n/**\n * Required operations with NO TOOL MAPPED AT ALL.\n *\n * <b>The gap none of the selectors below could report.</b> Every other selector here keys on\n * `unmappedRequiredCanonicalFields`, which describes a FIELD-translation gap. An operation that\n * declares no required canonical fields therefore has an empty array whether or not a tool answers\n * it - so a required operation with nothing mapped to it was invisible to this banner entirely.\n *\n * That is not a corner case. `find-companies` on the sales prospecting module declares no request\n * fields, so removing its tool mapping left the module's own page silent while the module selector\n * beside it marked the module as needing attention. An operator was told there was a problem by a\n * chip in a dropdown and given nowhere on the page to find out what.\n *\n * Reported SEPARATELY from the field selectors rather than folded into them, because the remedy\n * differs: these need a tool chosen, and field translation is a question that only arises once one\n * is. A consumer rendering both must therefore exclude these from the field lists - see\n * `MappingCompletenessBanner` - or an operation with both problems appears twice and the more\n * urgent half is buried under the less urgent one.\n */\nexport function selectRequiredUnmappedOperations(\n operations: readonly MappingCompletenessItem[],\n): MappingCompletenessItem[] {\n return operations.filter((op) => op.isRequired && !op.isToolMapped);\n}\n\nexport function selectRequiredIncompleteOperations(\n operations: MappingCompletenessItem[],\n): MappingCompletenessItem[] {\n return operations.filter((op) => op.isRequired && hasUnmappedRequiredField(op));\n}\n\n/**\n * OPTIONAL operations with a gap. Informational only: the operation powers an extra capability that\n * stays off until its fields are mapped, and the connector may legitimately expose no suitable tool.\n *\n * @remarks\n * Treat these as a note, never a warning. A connector need not expose every operation a module can\n * use, so a correctly configured connector can sit here indefinitely — and a banner that never\n * clears is a banner operators learn to ignore, which costs you the required warnings too.\n */\nexport function selectOptionalIncompleteOperations(\n operations: MappingCompletenessItem[],\n): MappingCompletenessItem[] {\n return operations.filter((op) => !op.isRequired && hasUnmappedRequiredField(op));\n}\n\n/** A module's readiness, counted over REQUIRED operations only. */\nexport interface MappingReadiness {\n /** Required operations that are mapped AND have no field gaps. */\n complete: number;\n /** Total required operations declared by the module's catalogue. */\n total: number;\n /** True when every required operation is mapped and complete. */\n isReady: boolean;\n}\n\n/**\n * Readiness over REQUIRED operations only, for a progress indicator or a \"ready\" badge.\n *\n * @remarks\n * Optional operations are excluded on purpose. Counting them would leave a properly configured\n * connector permanently short of 100%, for operations the operator was never expected to map. This\n * rule came from `ethisyscore-plugin-timesheets`, which had it right while another plugin was\n * reporting optional field gaps in a standing banner.\n */\nexport function selectMappingReadiness(\n operations: MappingCompletenessItem[],\n): MappingReadiness {\n const required = operations.filter((op) => op.isRequired);\n const complete = required.filter(\n (op) => op.isToolMapped && op.unmappedRequiredCanonicalFields.length === 0,\n ).length;\n\n return {\n complete,\n total: required.length,\n isReady: required.length > 0 && complete === required.length,\n };\n}\n","import {\n useQueries,\n useQuery,\n type QueryKey,\n type UseQueryOptions,\n type UseQueryResult,\n} from \"@tanstack/react-query\";\n\n/**\n * Plugin shim for the monolith's `useAuthenticatedQuery`.\n *\n * In the host realm the MCP transport is already authenticated and scoped to\n * the active organisation, so there is no auth gate to apply. The hook keeps\n * the monolith's return shape (`isLoading` + `isAuthLoading`) so lifted data\n * hooks and pages consume it unchanged — `isAuthLoading` is always `false`.\n */\nexport interface AuthenticatedQueryResult<TData, TError>\n extends Omit<UseQueryResult<TData, TError>, \"isLoading\"> {\n isLoading: boolean;\n isAuthLoading: boolean;\n}\n\nexport function useAuthenticatedQuery<\n TQueryFnData = unknown,\n TError = Error,\n TData = TQueryFnData,\n TQueryKey extends QueryKey = QueryKey,\n>(\n options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,\n): AuthenticatedQueryResult<TData, TError> {\n const result = useQuery(options);\n return {\n ...result,\n isLoading: result.isLoading,\n isAuthLoading: false,\n };\n}\n\nexport function useAuthenticatedQueries<TQueryFnData = unknown, TError = Error>(\n queries: UseQueryOptions<TQueryFnData, TError>[],\n) {\n return useQueries({ queries });\n}\n","import {\n connectorMappingCompletenessQueryKey,\n connectorMappingsQueryKey,\n} from \"../components/connectors/mappingQueryKeys\";\nimport {\n selectMappingReadiness,\n type MappingReadiness,\n} from \"../components/connectors/mappingCompleteness\";\nimport { useAuthenticatedQuery } from \"../platform-react/useAuthenticatedQuery\";\nimport type { IConnectorMappingService } from \"./IConnectorMappingService\";\nimport type { ConnectorMappingItem, MappingCompletenessResult } from \"./types\";\n\n/**\n * What every hook in this module needs to address one connector + module pair.\n *\n * `moduleKey` is a plain input, never a constant baked into the hook, and that is what makes\n * multi-module usage first-class rather than an afterthought: a consumer with twelve catalogues\n * passes whichever the operator selected and the query keys, the invalidation and the auto-match\n * trigger all re-key with it. A consumer with one catalogue passes a module-scope constant. Two of\n * the three plugins this was promoted from hardcoded their single module INSIDE the hook, which is\n * why the twelve-module one could not reuse them.\n */\nexport interface ConnectorMappingScope {\n /** Mapping transport. Injected so a consumer can supply a mock or its own. */\n service: IConnectorMappingService;\n\n /**\n * Selected connector, or `undefined` before the operator has chosen one. Reads stay disabled\n * while it is absent rather than firing against a placeholder id.\n */\n connectorId: string | undefined;\n\n /** Wire key the platform stores mappings under, e.g. `repo-monitoring`. */\n moduleKey: string;\n}\n\n/** Return shape of {@link useConnectorMappings}. */\nexport interface UseConnectorMappingsResult {\n mappings: ConnectorMappingItem[];\n isLoading: boolean;\n isFetching: boolean;\n\n /**\n * When the current data was fetched. A real timestamp, not a placeholder: the auto-match trigger\n * gates on this being newer than the last connector switch, so a constant would either fire on\n * stale data or never fire at all.\n */\n dataUpdatedAt: number;\n\n error: Error | undefined;\n refetch: () => void;\n}\n\n/**\n * The mapping rows for one connector + module. Pure read state; pair it with\n * `useConnectorMappingMutations` for the write side, which reads this hook's cache entry to keep a\n * partial write from destroying a translation.\n *\n * @remarks\n * No `placeholderData`. The auto-match trigger inspects `mappings.length` immediately after a\n * connector switch, and keeping the previous connector's rows visible would make an unmapped\n * connector look mapped and skip the trigger.\n */\nexport function useConnectorMappings({\n service,\n connectorId,\n moduleKey,\n}: ConnectorMappingScope): UseConnectorMappingsResult {\n const query = useAuthenticatedQuery<ConnectorMappingItem[]>({\n queryKey: connectorMappingsQueryKey(connectorId, moduleKey),\n queryFn: () => service.getMappings(connectorId!, moduleKey),\n enabled: !!connectorId && !!moduleKey,\n meta: { errorTitle: \"Error loading tool mappings\" },\n });\n\n return {\n mappings: query.data ?? [],\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n dataUpdatedAt: query.dataUpdatedAt,\n error: query.error ?? undefined,\n refetch: () => void query.refetch(),\n };\n}\n\n/** Return shape of {@link useConnectorMappingCompleteness}. */\nexport interface UseConnectorMappingCompletenessResult {\n operations: MappingCompletenessResult[\"operations\"];\n\n /**\n * Readiness over REQUIRED operations only, so a correctly configured connector can reach 100%.\n * Counting optional operations would leave it permanently short, for operations the operator was\n * never expected to map. See `selectMappingReadiness`.\n */\n readiness: MappingReadiness;\n\n isLoading: boolean;\n isFetching: boolean;\n}\n\n/**\n * Per-operation mapping completeness — the data behind `MappingCompletenessBanner`.\n *\n * @remarks\n * Keyed with {@link connectorMappingCompletenessQueryKey}, which is a CHILD of the mappings key.\n * That nesting is load-bearing: completeness is the mappings joined against the live tool schemas,\n * so every write can change it, and as sibling keys a mutation had to remember to invalidate two.\n * One plugin invalidated only the mappings key and its banner went stale after every save, clear\n * and auto-match until the operator navigated away.\n */\nexport function useConnectorMappingCompleteness({\n service,\n connectorId,\n moduleKey,\n}: ConnectorMappingScope): UseConnectorMappingCompletenessResult {\n const query = useAuthenticatedQuery<MappingCompletenessResult>({\n queryKey: connectorMappingCompletenessQueryKey(connectorId, moduleKey),\n queryFn: () => service.getMappingCompleteness(connectorId!, moduleKey),\n enabled: !!connectorId && !!moduleKey,\n meta: { errorTitle: \"Error loading mapping completeness\" },\n });\n\n const operations = query.data?.operations ?? [];\n\n return {\n operations,\n readiness: selectMappingReadiness(operations),\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n };\n}\n","/**\n * Host toast bridge — the ergonomic surface a lifted monolith `useToast()`\n * expects, shipped alongside the other shared composites so a plugin's\n * `components/shared/*` (and its `use-toast` alias) resolve here.\n *\n * PlatformReact pages can't import the host's toaster (it's an app-global\n * singleton, not in the host module registry), so these dispatch the\n * `ethisys:toast` CustomEvent the host's toast listener (`useExtensionToast`)\n * forwards to its toaster — the same wire contract as the lower-level\n * `@ethisyscore/components-react` `showToast` primitive (kept self-contained\n * here to avoid a load-bearing cross-package import for a one-line dispatch).\n * Outside a host that wired the listener (mock/dev), every call is a harmless\n * no-op, so a plugin keeps its lifted `showSuccessToast(...)` call sites.\n */\ntype ToastVariant = \"default\" | \"success\" | \"error\" | \"warning\";\n\nfunction dispatchToast(title: string, description: string | undefined, variant: ToastVariant): void {\n if (typeof window === \"undefined\" || title.length === 0) {\n return;\n }\n window.dispatchEvent(new CustomEvent(\"ethisys:toast\", { detail: { title, description, variant } }));\n}\n\nexport function showSuccessToast(title: string, description?: string): void {\n dispatchToast(title, description, \"success\");\n}\n\nexport function showErrorToast(title: string, description?: string): void {\n dispatchToast(title, description, \"error\");\n}\n\nexport function showWarningToast(title: string, description?: string): void {\n dispatchToast(title, description, \"warning\");\n}\n\n/**\n * Options accepted by the `toast(...)` returned from {@link useToast}. Both `title`\n * and `description` are optional, but at least one must be set — `toast({ description })`\n * shows the description as the message (mirroring the monolith); `toast({})` is a no-op.\n */\nexport interface ToastOptions {\n /** Primary line. Falls back to {@link ToastOptions.description} when absent/empty. */\n title?: string;\n /** Secondary line — or the message itself when no `title` is given. */\n description?: string;\n /** `\"destructive\"`/`\"error\"` → error; `\"warning\"`/`\"success\"` map directly; else default. */\n variant?: string;\n}\n\n/**\n * Hook mirror of the monolith's `useToast()` surface, so a lifted\n * `const { toast } = useToast()` (and `showSuccessToast`/`showErrorToast`) keeps\n * working unchanged once `@/hooks/common/utility/use-toast` is aliased here.\n */\nexport function useToast() {\n return {\n toast: (opts: ToastOptions): void => {\n const variant: ToastVariant =\n opts.variant === \"destructive\" || opts.variant === \"error\"\n ? \"error\"\n : opts.variant === \"warning\"\n ? \"warning\"\n : opts.variant === \"success\"\n ? \"success\"\n : \"default\";\n // `title` is the primary line; fall back to `description` so `toast({ description })`\n // shows it (mirroring the monolith) instead of silently no-op'ing. Both absent → no-op.\n if (opts.title) {\n dispatchToast(opts.title, opts.description, variant);\n } else if (opts.description) {\n dispatchToast(opts.description, undefined, variant);\n }\n },\n showSuccessToast,\n showErrorToast,\n showWarningToast,\n };\n}\n","/**\n * Wire shapes for the connector tool-mapping surface — the DTOs the host's `connectors:mappings:*`\n * tools return, plus the argument shapes the hooks in this module accept.\n *\n * Declared here rather than in a plugin's generated contracts for the same reason the completeness\n * shapes moved to `components/connectors/types`: this package sits BELOW the plugins, and three of\n * them held their own copy of every type below. The copies are what let the surfaces drift.\n *\n * The rendering-side shapes (`MappingCompletenessItem`, `ConnectorCanonicalField`,\n * `TestToolMappingRequest`, …) are NOT redeclared here. They already live in\n * `components/connectors/types` next to the components that consume them, and are re-exported by\n * this module's barrel so a consumer needs one import.\n */\nimport type {\n ConnectorCanonicalField,\n ConnectorMappingSuggestionResult,\n MappingCompletenessResult,\n TestToolMappingResult,\n} from \"../components/connectors/types\";\n\n// ---------------------------------------------------------------------------\n// Wire vocabularies\n//\n// `as const` objects rather than bare unions so a render path can compare against a named member.\n// The union types are derived from them, so the two cannot drift.\n// ---------------------------------------------------------------------------\n\n/** How a mapping came to exist. Mirrors the backend `ToolMappingSource` enum (snake_case wire). */\nexport const MAPPING_SOURCE = {\n keyword: \"keyword\",\n ai: \"ai\",\n manual: \"manual\",\n} as const;\n\nexport type ConnectorMappingSource = (typeof MAPPING_SOURCE)[keyof typeof MAPPING_SOURCE];\n\n/** Why the AI matcher was unavailable for an auto-match run. `null` means it was available. */\nexport const AI_UNAVAILABLE_REASON = {\n Timeout: \"timeout\",\n ProviderError: \"provider_error\",\n ParseError: \"parse_error\",\n ContextTooLarge: \"context_too_large\",\n NoConnectorConfigured: \"no_connector_configured\",\n} as const;\n\nexport type ConnectorAiUnavailableReason =\n | (typeof AI_UNAVAILABLE_REASON)[keyof typeof AI_UNAVAILABLE_REASON]\n | null;\n\n/** Why a concurrent auto-match request was refused. `null` means no conflict. */\nexport const AUTO_MATCH_CONFLICT_REASON = {\n AnotherAutoMatchInProgress: \"another_auto_match_in_progress\",\n} as const;\n\nexport type ConnectorAutoMatchConflictReason =\n | (typeof AUTO_MATCH_CONFLICT_REASON)[keyof typeof AUTO_MATCH_CONFLICT_REASON]\n | null;\n\n/**\n * What a mapping resolves to, as this surface spells it: a connector to a third-party system, or\n * another installed application.\n *\n * Lower-case, and NOT the string the host stores. The host persists its `ToolMappingTargetKind` by\n * NAME and case (`\"Connector\"` / `\"Plugin\"`), so the two vocabularies are declared separately and\n * converted by `toWireTargetKind` / `fromWireTargetKind` in `targetSelection`. Sharing one literal\n * would make a render-side comparison and a wire value the same string by coincidence, and the\n * first time either side changed case the other would fail silently - the stored value matching\n * neither branch of the host's resolver nor its CHECK constraint.\n */\nexport const MAPPING_TARGET_KIND = {\n connector: \"connector\",\n plugin: \"plugin\",\n} as const;\n\nexport type MappingTargetKind = (typeof MAPPING_TARGET_KIND)[keyof typeof MAPPING_TARGET_KIND];\n\n/**\n * Which kind of thing serves a module. The wire values are the host's\n * `ToolMappingTargetKind` member NAMES, so they are a contract rather than a display label.\n */\nexport const MODULE_PROVIDER_KIND = {\n Connector: \"Connector\",\n Plugin: \"Plugin\",\n} as const;\n\nexport type ModuleProviderKind = (typeof MODULE_PROVIDER_KIND)[keyof typeof MODULE_PROVIDER_KIND];\n\n/**\n * The same vocabulary as the host stores and matches it, keyed the way the UI spells its kinds so a\n * render-side value converts without a cast. See {@link MAPPING_TARGET_KIND}.\n *\n * The STRINGS come from {@link MODULE_PROVIDER_KIND} rather than being retyped, because this file\n * arrived at the same contract twice - once for the per-operation target and once for the\n * per-module provider - and two literal copies of a value the host matches by name is the drift\n * this vocabulary split exists to prevent, not an instance of it.\n */\nexport const MAPPING_TARGET_KIND_WIRE = {\n connector: MODULE_PROVIDER_KIND.Connector,\n plugin: MODULE_PROVIDER_KIND.Plugin,\n} as const;\n\nexport type MappingTargetKindWire =\n (typeof MAPPING_TARGET_KIND_WIRE)[keyof typeof MAPPING_TARGET_KIND_WIRE];\n\n// ---------------------------------------------------------------------------\n// Mapping rows\n// ---------------------------------------------------------------------------\n\n/**\n * The operator-editable translation attached to one mapping row.\n *\n * @remarks\n * All three members are `null`-able and **all three must be sent on every write**. The host's\n * `connectors:mappings:set` treats an omitted or null member as \"no operator override\" and writes\n * NULL, so a partial write silently destroys whichever members it left out. This is not a style\n * point: picking a tool used to clear an operator's field translations, confirmed against the\n * production database. {@link ConnectorMappingWriteArgs} and `useConnectorMappingMutations` are what\n * make a write complete; see the fill-in in `useConnectorMappingMutations`.\n */\nexport interface ConnectorMappingTranslationState {\n /** Canonical request field → tool input field name. `null` ⇒ identity translation. */\n requestFieldMap: Record<string, string> | null;\n\n /** Canonical response field → JSON path against the tool's response. `null` ⇒ identity. */\n responseFieldPaths: Record<string, string> | null;\n\n /**\n * Constant tool-request parameters injected into every call regardless of the canonical input\n * (tool field name → literal value, e.g. `method: \"get_reviews\"`). `null` ⇒ none configured.\n */\n staticRequestFields: Record<string, string> | null;\n}\n\n/** One row of the operation → tool mapping table, as `connectors:mappings:list` returns it. */\nexport interface ConnectorMappingItem {\n /** Canonical operation key, e.g. `list-commits`. */\n operationKey: string;\n\n /** Human label, from the module's catalogue. */\n label: string;\n\n /** Whether the module declares this operation as required. */\n isRequired: boolean;\n\n /** Tool the operation currently resolves to, or `null` when unmapped. */\n mappedToolName: string | null;\n\n source: ConnectorMappingSource | null;\n\n /** AI match confidence in `0..1`, or `null` for keyword/manual matches. */\n confidence: number | null;\n\n /**\n * Opaque concurrency token. The host declares it as `byte[]`, which crosses the wire as base64:\n * read one here and hand the same string back on write. `null` means no stored row exists yet,\n * which callers send as `\"\"` so the host upserts.\n */\n rowVersion: string | null;\n\n /** True when the row is an AI match below the auto-accept threshold and wants an operator's eye. */\n requiresReview?: boolean;\n\n /**\n * Canonical request schema declared by the operation's owning module. `null` when the operation\n * declares none, in which case there is nothing operator-meaningful to translate and the\n * translation affordance stays hidden.\n */\n canonicalRequestFields?: ConnectorCanonicalField[] | null;\n\n /** Canonical response schema. Same null semantics as {@link canonicalRequestFields}. */\n canonicalResponseFields?: ConnectorCanonicalField[] | null;\n\n requestFieldMap?: Record<string, string> | null;\n responseFieldPaths?: Record<string, string> | null;\n staticRequestFields?: Record<string, string> | null;\n}\n\n// ---------------------------------------------------------------------------\n// Auto-match\n// ---------------------------------------------------------------------------\n\n/** One operation's outcome from an auto-match run. Mirrors `ConnectorAutoMatchItem`. */\nexport interface ConnectorAutoMatchItem {\n operationKey: string;\n\n /**\n * `null` when this operation matched nothing. An item is returned for EVERY operation regardless,\n * so `items.length` is the operation count and not the match count — reading it as the latter\n * reports success on a run that mapped nothing.\n */\n mappedToolName: string | null;\n\n source: ConnectorMappingSource | null;\n confidence: number | null;\n requiresReview?: boolean;\n\n /**\n * A tool that keyword-matched but was rejected, and the required parameters it could not be\n * satisfied with. The host has always returned both; the types this was promoted from omitted\n * them in two of three plugins, which discarded the only machine-readable explanation of why an\n * auto-match came back empty.\n */\n discardedToolName?: string | null;\n unsatisfiedToolParameters?: string[] | null;\n}\n\n/** Raw auto-match result, before {@link AutoMatchSummary} reduces it for the banner. */\nexport interface AutoMatchResponse {\n items: ConnectorAutoMatchItem[];\n aiAvailable: boolean;\n aiUnavailableReason: ConnectorAiUnavailableReason;\n unmatchedRequiredCount: number;\n conflictReason: ConnectorAutoMatchConflictReason;\n}\n\n/** Per-source counts reduced from an {@link AutoMatchResponse}, for the outcome banner. */\nexport interface AutoMatchSummary {\n aiAvailable: boolean;\n aiUnavailableReason: ConnectorAiUnavailableReason;\n unmatchedRequiredCount: number;\n matchedByKeyword: number;\n matchedByAi: number;\n conflictReason: ConnectorAutoMatchConflictReason;\n}\n\n// ---------------------------------------------------------------------------\n// Write / request bodies\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for one mapping write.\n *\n * The three translation members are OPTIONAL here and required on the wire, and the gap between\n * those two facts is deliberate: a caller that is only changing the tool omits them, and\n * `useConnectorMappingMutations` fills each one from the row it already holds before the write\n * leaves. Omitting a member therefore means \"leave it as it is\", never \"clear it\". A caller that\n * genuinely wants to clear one passes an explicit `null`.\n */\nexport interface ConnectorMappingWriteArgs {\n operationKey: string;\n\n /** `\"\"` is the unmap request; the hooks route it to `connectors:mappings:delete`. */\n toolName: string;\n\n /** Row version read from {@link ConnectorMappingItem.rowVersion}, or `\"\"` for a fresh row. */\n rowVersion: string;\n\n requestFieldMap?: Record<string, string> | null;\n responseFieldPaths?: Record<string, string> | null;\n staticRequestFields?: Record<string, string> | null;\n}\n\n/** Body for the per-operation AI translation suggestion. */\nexport interface SuggestMappingTranslationBody {\n operationKey: string;\n toolName: string;\n\n /** Sample values the operator typed. The host declares this non-nullable; absent ⇒ `{}`. */\n sampleArgs?: Record<string, unknown>;\n}\n\n/** Body for the translation dry-run. `connectorId` / `moduleKey` are passed alongside, not here. */\nexport interface TestMappingBody {\n operationKey: string;\n toolName: string;\n requestFieldMap: Record<string, string> | null;\n responseFieldPaths: Record<string, string> | null;\n\n /** The DRAFT static params, so the dry-run exercises the discriminator the operator is testing. */\n staticRequestFields: Record<string, string> | null;\n sampleArgs: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Connector enumeration (the picker's data, not the mapping surface's)\n// ---------------------------------------------------------------------------\n\n/** A connector as `settings:list-connectors` projects it. */\nexport interface ConnectorSummary {\n id: string;\n name: string;\n slug?: string | null;\n isEnabled: boolean;\n}\n\n/** One tool a connector exposes, as `settings:get-connector` projects it. */\nexport interface ConnectorToolSummary {\n toolName: string;\n description?: string | null;\n isEnabled: boolean;\n}\n// ---------------------------------------------------------------------------\n// Per-module provider selection\n//\n// The level ABOVE the per-operation mapping table, and keeping the two distinct is the point. This\n// records which provider serves a module; the table below it records which of that provider's tools\n// answers each operation. The host stores them as separate rows for a reason worth repeating: its\n// auto-match writes a candidate mapping row per connector, so the mapping table on its own cannot\n// answer \"where does this module get its data from\", and any rule for picking between several rows\n// without a recorded selection - highest confidence, most recent, lowest id - is a guess dressed up\n// as a policy.\n// ---------------------------------------------------------------------------\n\n/**\n * Which provider an organisation has chosen to serve a module.\n *\n * @remarks\n * `targetKind` is `null` when the organisation has made NO choice, and the host deliberately does\n * not default it to `\"Connector\"`. \"Nobody has chosen yet\" and \"chose a connector that has no\n * mapping yet\" call for different remedies, and a surface that cannot tell them apart shows a\n * choice nobody made.\n */\nexport interface ModuleProvider {\n targetKind: ModuleProviderKind | null;\n connectorId: string | null;\n targetExtensionId: string | null;\n\n /**\n * Concurrency token of the stored selection, `null` when there is none yet.\n *\n * Opaque, exactly like a mapping's: read it here and hand the same string back to\n * `setModuleProvider`. Nothing on this side parses or constructs one.\n */\n rowVersion: string | null;\n}\n\n/** One tool an installed application exposes, as the provider picker renders it. */\nexport interface PluginProviderTool {\n /** Fully namespaced, e.g. `hr:get-employees-by-ids`. A plugin tool routes under this name verbatim. */\n toolName: string;\n description?: string | null;\n}\n\n/**\n * One installed application an operator may choose to serve a module, with the tools it can answer\n * the module's operations with.\n *\n * @remarks\n * **The candidate set is narrower than \"every tool that routes\", and has to be.** The host's write\n * path refuses a plugin-target mapping naming a tool the CONSUMING application did not declare in\n * its own manifest `mcpToolDependencies`, because such a row is authority to invoke another\n * application's tool with no per-tool permission check. A picker offering a wider list offers\n * choices the save will reject, so whatever supplies this must apply the same bound - and must also\n * filter by the caller's own permissions, which the host's routable-tool listing explicitly does\n * not.\n *\n * There is no endpoint that returns this, in any transport, which is why it is an INPUT to\n * `useConnectorToolMappings` rather than something this package fetches.\n */\nexport interface PluginProviderCandidate {\n extensionId: string;\n\n /** Display name of the installed application. */\n name: string;\n\n tools: PluginProviderTool[];\n}\n\n/**\n * A mapping row whose target is another installed application, as the plugin-mode table renders it.\n *\n * Same shape as {@link ConnectorMappingItem} plus the providing extension, which a connector-scoped\n * row has no need of because the connector is the scope of the read.\n */\nexport interface PluginTargetMappingItem extends ConnectorMappingItem {\n /** The providing extension, or `null` when the operation is unmapped. */\n targetExtensionId: string | null;\n}\n\n/** Arguments for one plugin-target mapping write. */\nexport interface SetPluginTargetMappingArgs {\n operationKey: string;\n\n /** The PROVIDING extension. Required by the host even on an unmap, which is how it finds the row. */\n targetExtensionId: string;\n\n /** `\"\"` is the unmap request, matching {@link ConnectorMappingWriteArgs.toolName}. */\n toolName: string;\n\n /** Row version, or `\"\"` for a fresh row. Not enforced by the host on the unmap branch. */\n rowVersion: string;\n}\n\n/** The stored plugin-target mapping after a write. */\nexport interface SetPluginTargetMappingResult {\n /** `null` when the operation is now unmapped. */\n toolName: string | null;\n targetExtensionId: string | null;\n\n /** `\"\"` after an unmap, where there is no row left to hold a token. */\n rowVersion: string;\n}\n\n// Re-exported so a consumer of this module's types does not need a second import for the shapes\n// the service returns. Type-only, so nothing is added to the built bundle.\nexport type {\n ConnectorCanonicalField,\n ConnectorMappingSuggestionResult,\n MappingCompletenessResult,\n TestToolMappingResult,\n};\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useMutation, useQueryClient } from \"@tanstack/react-query\";\n\nimport { connectorMappingsQueryKey } from \"../components/connectors/mappingQueryKeys\";\nimport {\n showSuccessToast as defaultShowSuccessToast,\n showWarningToast as defaultShowWarningToast,\n} from \"../components/shared/toast\";\nimport type { ConnectorMappingScope } from \"./useConnectorMappings\";\nimport {\n MAPPING_SOURCE,\n type AutoMatchResponse,\n type AutoMatchSummary,\n type ConnectorMappingItem,\n type ConnectorMappingWriteArgs,\n} from \"./types\";\n\n/** Identifies the row a delete targets, with the version it was read at. */\ninterface DeleteMappingArgs {\n operationKey: string;\n rowVersion: string;\n}\n\n/**\n * Per-call outcome callbacks for one mapping write.\n *\n * Additive and optional, for a caller that SEQUENCES this write after another and has to know which\n * of the two failed — \"the scope saved but the pin did not\" is a different situation for the\n * operator than either write failing alone. The shared error toast still fires either way; these\n * only let the caller record which half it was.\n */\nexport interface MappingWriteCallbacks {\n onSuccess?: () => void;\n onError?: () => void;\n}\n\n/** Options for {@link useConnectorMappingMutations}. */\nexport interface UseConnectorMappingMutationsOptions extends ConnectorMappingScope {\n /**\n * Called after any mutation that SAVES a mapping, with the connector and module it was saved\n * against.\n *\n * @remarks\n * Exists so a consumer can mirror the operator's connector choice into its own state. One plugin\n * must: its transcription and feed-poll tasks are background workers with no caller identity, so\n * they cannot enumerate connectors to discover which one holds the mapping, and without this\n * write-through they report themselves unconfigured no matter what the Tool Mappings page shows.\n * Taken as a callback so this hook keeps no domain knowledge of what a consumer stores.\n *\n * **It hangs off the mutations rather than the page's click handlers deliberately.** There are\n * three distinct ways to save a mapping — picking a tool, auto-matching, and saving field\n * translations — and the third was originally missed, which left an operator who had just fixed a\n * field mapping with a lane that still reported itself unconfigured and no way to tell why. Every\n * save funnels through here, so a fourth route cannot reintroduce that.\n *\n * Deliberately NOT called by a delete: clearing a mapping is not choosing a connector. Leaving\n * the recorded id in place makes the lane report \"a connector is chosen but has no tool mapped\",\n * which is both accurate and the actionable half.\n */\n onMappingSaved?: (connectorId: string, moduleKey: string) => void;\n\n /**\n * Success-toast sink. Defaults to the SDK's `showSuccessToast`, which dispatches the host's\n * `ethisys:toast` event and no-ops outside a host that wired the listener. Override only to\n * silence it or to route it somewhere else — the three plugins this was promoted from all\n * ultimately reached the same host event.\n */\n showSuccessToast?: (title: string, description?: string) => void;\n\n /**\n * Warning-toast sink, for the save-time gate's findings. Same defaulting and same reason as\n * {@link showSuccessToast}; separate because a finding reported through the SUCCESS sink reads as\n * part of \"saved successfully\", which is a warning nobody acts on.\n */\n showWarningToast?: (title: string, description?: string) => void;\n}\n\n/** Return shape of {@link useConnectorMappingMutations}. */\nexport interface UseConnectorMappingMutationsResult {\n /** Runs auto-match for the whole module. No-ops without a connector. */\n triggerAutoMatch: () => void;\n isAutoMatching: boolean;\n\n /**\n * Saves one mapping, or clears it when `toolName` is `\"\"`. See the fill-in in the implementation\n * for why omitting a translation member means \"leave it alone\" and not \"clear it\".\n */\n updateMapping: (args: ConnectorMappingWriteArgs, callbacks?: MappingWriteCallbacks) => void;\n isUpdating: boolean;\n\n /** Outcome of the most recent auto-match run, or `null` before one has completed. */\n autoMatchSummary: AutoMatchSummary | null;\n}\n\n/**\n * Resolves one translation member for a write: what the caller said, or the stored value when the\n * caller did not mention it.\n *\n * @remarks\n * **The `undefined` / `null` distinction is the whole function**, and getting it wrong is a defect\n * in each direction:\n *\n * - `undefined` — the caller did not mention this member. The tool picker saves three identity\n * arguments and nothing else, so filling from the stored row is what stops choosing a tool from\n * destroying the operator's field mapping. That was the production defect.\n * - `null` — the caller SAID clear it. The translation editor delivers `null` for a side whose\n * every input the operator emptied.\n *\n * The three copies this was promoted from wrote `args.requestFieldMap ?? current?.requestFieldMap\n * ?? null`, and `??` treats `null` and `undefined` alike — so an explicit clear was overwritten\n * from the cache and the editor could not clear a side at all. The operator emptied every input,\n * pressed Save, got a success toast, and the old mapping came straight back on the next refetch.\n * That is the mirror image of the defect the fill-in was added for, and it was in all three repos.\n */\n/**\n * {@link ConnectorMappingWriteArgs} after {@link resolveTranslationMember} has run: the three\n * translation members are no longer optional, because each has been decided.\n *\n * Naming it makes the boundary a TYPE rather than a comment. The service now REQUIRES all three —\n * an omitted argument there is indistinguishable from a deliberate clear — and this is what proves\n * the only route to it has resolved them. Without it the internal mutation would happily forward an\n * unresolved `undefined`, which is the same data loss one layer up.\n */\ntype ResolvedMappingWriteArgs = Omit<\n ConnectorMappingWriteArgs,\n \"requestFieldMap\" | \"responseFieldPaths\" | \"staticRequestFields\"\n> & {\n requestFieldMap: Record<string, string> | null;\n responseFieldPaths: Record<string, string> | null;\n staticRequestFields: Record<string, string> | null;\n};\n\nfunction resolveTranslationMember(\n supplied: Record<string, string> | null | undefined,\n stored: Record<string, string> | null | undefined,\n): Record<string, string> | null {\n if (supplied !== undefined) return supplied;\n return stored ?? null;\n}\n\n/**\n * Reduces an auto-match response for the outcome banner.\n *\n * Null-tolerant on purpose: the command returned no payload at all until the host gained a response\n * type, and a `Result` with no value is still representable on the wire. Throwing here would reject\n * inside `onSuccess`, where nothing handles it — a banner that reports nothing beats a crashed page.\n */\nfunction summarise(response: AutoMatchResponse | null | undefined): AutoMatchSummary {\n const items = response?.items ?? [];\n return {\n aiAvailable: response?.aiAvailable ?? false,\n aiUnavailableReason: response?.aiUnavailableReason ?? null,\n unmatchedRequiredCount: response?.unmatchedRequiredCount ?? 0,\n matchedByKeyword: items.filter((i) => i.source === MAPPING_SOURCE.keyword).length,\n matchedByAi: items.filter((i) => i.source === MAPPING_SOURCE.ai).length,\n conflictReason: response?.conflictReason ?? null,\n };\n}\n\n/**\n * The write side of the connector tool-mapping surface: auto-match, per-operation save, and clear.\n *\n * **This hook is the reason this module exists.** It was duplicated byte-for-byte across three\n * plugins and both production defects on this surface lived inside it:\n *\n * 1. **A stale completeness banner.** Two plugins declared their own query keys, one as siblings,\n * and its mutations invalidated only the mappings key. Fixed by nesting the completeness key\n * beneath the mappings key so one prefix invalidation covers both — see\n * {@link connectorMappingsQueryKey} — and there is nothing left for a mutation to forget.\n *\n * 2. **Silently destroyed field translations.** The tool-level save omitted the three translation\n * members, and because `connectors:mappings:set` treats an omitted member as \"no operator\n * override\" and writes NULL, picking a tool wiped the operator's field mapping and any pinned\n * static parameter with it. Confirmed against the production database. Fixed by the fill-in\n * below. It had to be fixed three times, in three repos, because this function was identical in\n * each — and one of the three never received the fix at all.\n *\n * 3. **The translation editor could not CLEAR a side**, which is (2)'s fix taken one step too far.\n * See {@link resolveTranslationMember}: it was present in all three repos and was found by this\n * module's tests rather than in production.\n */\nexport function useConnectorMappingMutations({\n service,\n connectorId,\n moduleKey,\n onMappingSaved,\n showSuccessToast = defaultShowSuccessToast,\n showWarningToast = defaultShowWarningToast,\n}: UseConnectorMappingMutationsOptions): UseConnectorMappingMutationsResult {\n const queryClient = useQueryClient();\n const [autoMatchSummary, setAutoMatchSummary] = useState<AutoMatchSummary | null>(null);\n\n // The summary belongs to the scope it was produced for. Without this, switching connector or\n // module leaves the previous scope's match counts and conflict state on screen — and if the new\n // scope already has mappings no auto-match runs to replace it, so the stale numbers simply stay.\n // Counts an operator reads as describing what they are looking at, describing something else, is\n // worse than showing nothing.\n const scopeKey = `${connectorId ?? \"\"}|${moduleKey}`;\n const summaryScopeRef = useRef<string>(scopeKey);\n useEffect(() => {\n if (summaryScopeRef.current === scopeKey) return;\n summaryScopeRef.current = scopeKey;\n setAutoMatchSummary(null);\n }, [scopeKey]);\n // Memoised because the callbacks below depend on it. The builder returns a fresh array every\n // call, so an unmemoised key would rebuild every callback on every render.\n const queryKey = useMemo(\n () => connectorMappingsQueryKey(connectorId, moduleKey),\n [connectorId, moduleKey],\n );\n\n // One invalidation covers the mappings read AND every query keyed beneath it, which is what\n // completeness is. Completeness is DERIVED from the mappings plus the live tool schemas, so any\n // write that changes a mapping can change the banner — including a clear, which can only ever\n // add a gap and is therefore the case where a stale banner is most misleading.\n const invalidateMappingsAndCompleteness = useCallback(() => {\n void queryClient.invalidateQueries({ queryKey });\n }, [queryClient, queryKey]);\n\n const autoMatch = useMutation({\n mutationFn: () => service.autoMatch(connectorId!, moduleKey),\n meta: { errorTitle: \"Auto-match failed\" },\n onSuccess: (response) => {\n setAutoMatchSummary(summarise(response));\n invalidateMappingsAndCompleteness();\n const matched = (response?.items ?? []).filter((i) => i.mappedToolName).length;\n // Only when something actually matched. A run that matched nothing chose no connector, so\n // there is nothing for a consumer to record.\n if (connectorId && matched > 0) {\n onMappingSaved?.(connectorId, moduleKey);\n }\n showSuccessToast(\"Auto-match complete\", `Matched ${matched} operations.`);\n },\n });\n\n const updateMapping = useMutation({\n mutationFn: (args: ResolvedMappingWriteArgs) =>\n service.updateMapping(\n connectorId!,\n moduleKey,\n args.operationKey,\n args.toolName,\n args.rowVersion,\n args.requestFieldMap,\n args.responseFieldPaths,\n args.staticRequestFields,\n ),\n meta: { errorTitle: \"Update failed\" },\n onSuccess: (outcome) => {\n invalidateMappingsAndCompleteness();\n if (connectorId) {\n onMappingSaved?.(connectorId, moduleKey);\n }\n\n // The gate's findings ride on a SUCCESS response, and until now nothing read them: this\n // callback took no argument, the service returned the row version alone, and the mapping\n // editor never rendered one. So a save that the platform had something true to say about\n // looked identical to a clean one — CON_MAP_020 among them, which had therefore never been\n // seen by an operator on any environment since it shipped.\n //\n // Reported as a WARNING toast rather than folded into the success line: a warning that reads\n // as part of \"saved successfully\" is a warning nobody acts on, and these are the findings the\n // gate deliberately does not refuse over.\n const findings = outcome?.findings ?? [];\n if (findings.length === 0) {\n showSuccessToast(\"Mapping updated\", \"The tool mapping was saved.\");\n return;\n }\n\n // The codes are in the description because they are what an operator searches for when they\n // come to ask about one.\n showWarningToast(\n findings.length === 1 ? \"Mapping saved, with a warning\" : `Mapping saved, with ${findings.length} warnings`,\n findings.map((f) => `${f.message}${f.detail ? ` ${f.detail}` : \"\"} (${f.code})`).join(\" \"),\n );\n },\n });\n\n const deleteMapping = useMutation({\n mutationFn: (args: DeleteMappingArgs) =>\n service.deleteMapping(connectorId!, moduleKey, args.operationKey, args.rowVersion),\n meta: { errorTitle: \"Remove failed\" },\n onSuccess: () => {\n invalidateMappingsAndCompleteness();\n showSuccessToast(\"Mapping removed\", \"The tool mapping was cleared.\");\n },\n });\n\n // No slug gate. The slug used to be threaded from a connector-detail fetch that could still be in\n // flight when a page auto-triggers on a connector switch, so the trigger silently skipped — one\n // of the reasons the Auto-match button appeared to do nothing. The host derives the namespace\n // from the connector id, so there is nothing to wait for.\n const triggerAutoMatch = useCallback(() => {\n if (!connectorId) return;\n autoMatch.mutate();\n }, [autoMatch, connectorId]);\n\n const updateOrDelete = useCallback(\n (args: ConnectorMappingWriteArgs, callbacks?: MappingWriteCallbacks) => {\n if (!connectorId) return;\n\n // A view emits `toolName === \"\"` when the operator clears the picker. The host's set\n // validator rejects an empty tool name, so a clear is routed to the delete tool rather than\n // surfacing as a validation error. Callers keep one `updateMapping` signature and never need\n // to know two host tools exist. An empty rowVersion means no row exists yet — nothing to\n // delete, so the clear is a no-op rather than a request that would 404.\n if (args.toolName === \"\") {\n if (args.rowVersion === \"\") return;\n deleteMapping.mutate(\n { operationKey: args.operationKey, rowVersion: args.rowVersion },\n callbacks,\n );\n return;\n }\n\n // Fill in any translation member the caller did not supply from the row we already hold.\n //\n // LOAD-BEARING, not tidiness. The platform store treats a null member as \"no operator\n // override\" and writes NULL, where a plugin's own table used to leave the column alone. So a\n // caller that changes ONLY the mapped tool — which is exactly what the tool picker does, and\n // what the one sequenced caller in `tech` does when it clears a pinned static parameter —\n // wipes the operator's field mapping and any other pinned parameter with it. Passing the\n // row's current translation back makes the write complete rather than partial.\n //\n // Auto-match is NOT one of these routes: it rewrites rows host-side and never reaches this\n // function. Its effect on a translation is the host's business, not this hook's.\n //\n // Only `undefined` — the caller not mentioning a member — is filled in. An explicit `null`\n // is a caller SAYING clear this and survives untouched; see `resolveTranslationMember`, and\n // do not collapse it back to `??`.\n //\n // Read from the query cache rather than taking the rows as an argument: this hook is used\n // alongside `useConnectorMappings`, which already populates that cache under the same key, so\n // threading the list through every caller would create a second source of the same truth.\n const rows = queryClient.getQueryData<ConnectorMappingItem[]>(queryKey) ?? [];\n const current = rows.find((m) => m.operationKey === args.operationKey);\n\n updateMapping.mutate(\n {\n ...args,\n requestFieldMap: resolveTranslationMember(args.requestFieldMap, current?.requestFieldMap),\n responseFieldPaths: resolveTranslationMember(\n args.responseFieldPaths,\n current?.responseFieldPaths,\n ),\n staticRequestFields: resolveTranslationMember(\n args.staticRequestFields,\n current?.staticRequestFields,\n ),\n },\n callbacks,\n );\n },\n [connectorId, updateMapping, deleteMapping, queryClient, queryKey],\n );\n\n return {\n triggerAutoMatch,\n isAutoMatching: autoMatch.isPending,\n updateMapping: updateOrDelete,\n isUpdating: updateMapping.isPending || deleteMapping.isPending,\n autoMatchSummary,\n };\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\n\nimport type { IConnectorMappingService } from \"./IConnectorMappingService\";\nimport type {\n ConnectorMappingSuggestionResult,\n SuggestMappingTranslationBody,\n TestMappingBody,\n TestToolMappingResult,\n} from \"./types\";\n\n/** Variables for one suggest-translation call. */\nexport interface SuggestMappingTranslationVars {\n connectorId: string;\n moduleKey: string;\n body: SuggestMappingTranslationBody;\n}\n\n/** Variables for one dry-run call. */\nexport interface TestMappingVars {\n connectorId: string;\n moduleKey: string;\n body: TestMappingBody;\n}\n\n/**\n * The opt-in \"Suggest with AI\" action.\n *\n * Read-only inference: it invalidates NOTHING, because nothing was written. The caller decides what\n * to do with the proposal — every consumer so far opens the translation editor seeded with it so\n * the operator reviews before saving.\n *\n * @remarks\n * A soft failure — AI not configured, or no confident suggestion — comes back as\n * `{ success: false, error }` rather than an HTTP error. This hook THROWS on it, so the consumer's\n * shared `meta.errorTitle` handler surfaces the reason. Resolving successfully instead is what made\n * \"Suggest with AI\" silently do nothing in the copy this was promoted from: the mutation settled\n * happily, `result.success` was false, and the caller's `onSuccess` returned early without telling\n * anyone why.\n *\n * The service itself never throws for this case (see `ConnectorMappingService`), so a consumer that\n * wants to render the soft failure inline rather than as a toast can call the service directly.\n */\nexport function useSuggestMappingTranslation(\n service: IConnectorMappingService,\n): UseMutationResult<ConnectorMappingSuggestionResult, Error, SuggestMappingTranslationVars> {\n return useMutation<ConnectorMappingSuggestionResult, Error, SuggestMappingTranslationVars>({\n mutationFn: async (vars) => {\n const result = await service.suggestMappingTranslation(\n vars.connectorId,\n vars.moduleKey,\n vars.body,\n );\n if (!result.success) {\n throw new Error(\n result.error ?? \"The AI couldn't suggest a translation for this operation.\",\n );\n }\n return result;\n },\n meta: { errorTitle: \"Suggest translation failed\" },\n });\n}\n\n/**\n * The \"Test mapping\" dry-run behind the translation editor's Test panel.\n *\n * Never throws for a tool-invocation or extraction failure: those come back as\n * `{ success: false, error }` and the editor renders them in its own panel, next to the raw\n * response the operator needs in order to fix the JSON path. Converting that into a toast would\n * discard the raw response, which is the only actionable half.\n *\n * Invalidates nothing — a dry run writes nothing.\n */\nexport function useTestMapping(\n service: IConnectorMappingService,\n): UseMutationResult<TestToolMappingResult, Error, TestMappingVars> {\n return useMutation<TestToolMappingResult, Error, TestMappingVars>({\n mutationFn: (vars) => service.testMapping(vars.connectorId, vars.moduleKey, vars.body),\n meta: { errorTitle: \"Mapping test failed\" },\n });\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport type { ConnectorMappingTranslationEditorProps } from \"../components/connectors/ConnectorMappingTranslationEditorView\";\nimport type { MappingCompletenessItem } from \"../components/connectors/types\";\nimport { useSuggestMappingTranslation, useTestMapping } from \"./useMappingAdvisors\";\nimport type { MappingWriteCallbacks } from \"./useConnectorMappingMutations\";\nimport type { IConnectorMappingService } from \"./IConnectorMappingService\";\nimport type {\n ConnectorMappingItem,\n ConnectorMappingTranslationState,\n ConnectorMappingWriteArgs,\n TestToolMappingResult,\n} from \"./types\";\n\n/** Which operation the editor is open for, and the translation it was seeded with. */\ninterface EditorState {\n operationKey: string;\n current: ConnectorMappingTranslationState;\n}\n\n/** Options for {@link useMappingTranslationEditor}. */\nexport interface UseMappingTranslationEditorOptions {\n service: IConnectorMappingService;\n\n /** `undefined` before a connector is selected; every action no-ops until then. */\n connectorId: string | undefined;\n\n moduleKey: string;\n\n /** The rows the editor looks a row up in — pass `useConnectorMappings`' `mappings` straight in. */\n mappings: ConnectorMappingItem[];\n\n /**\n * The write. Pass `useConnectorMappingMutations`' `updateMapping`, which is what routes the save\n * through the translation-preserving fill and reports it to `onMappingSaved`.\n */\n updateMapping: (args: ConnectorMappingWriteArgs, callbacks?: MappingWriteCallbacks) => void;\n}\n\n/** Return shape of {@link useMappingTranslationEditor}. */\nexport interface UseMappingTranslationEditorResult {\n /** The row being edited, or `undefined` when the editor is closed or the row vanished. */\n editorRow: ConnectorMappingItem | undefined;\n\n /** True when a row is being edited. Gate the dialog's mount on this, not on `editorRow`. */\n isOpen: boolean;\n\n /** Wire to `ConnectorMappingViewProps.onEditTranslation` — the per-row settings icon. */\n onEditTranslation: (operationKey: string, current: ConnectorMappingTranslationState) => void;\n\n /** Wire to `MappingCompletenessBannerProps.onOpenMappingEditor`. */\n onOpenMappingEditor: (\n operation: MappingCompletenessItem,\n seed?: ConnectorMappingTranslationState,\n ) => void;\n\n /** Wire to `MappingCompletenessBannerProps.onSuggestWithAi`. */\n onSuggestWithAi: (operation: MappingCompletenessItem) => void;\n\n /** Wire to `MappingCompletenessBannerProps.suggestingOperationKey` — drives the per-row spinner. */\n suggestingOperationKey: string | null;\n\n /**\n * Ready-to-spread props for `ConnectorMappingTranslationEditorView`, valid only while\n * {@link isOpen}. Mount the dialog conditionally: the component initialises its inputs lazily on\n * each mount, which is what keeps an operator's in-flight edits sovereign against a concurrent\n * refresh of the row.\n */\n editorProps: ConnectorMappingTranslationEditorProps;\n}\n\n/** The failure shape the editor's Test panel renders when there is nothing to test against. */\nfunction testUnavailable(error: string): TestToolMappingResult {\n return { success: false, error, rawResponseJson: null, extractedFields: [] };\n}\n\n/**\n * The translation editor's state machine: which row is open, what it was seeded with, and the three\n * ways it can be opened.\n *\n * This was identical in all three plugins that had a Tool Mappings page, down to the comments, and\n * it needs nothing plugin-specific — no routing, no identity, no persistence. It is therefore the\n * part of the page hook that could move wholesale, and the part where a fourth copy would be pure\n * loss.\n *\n * The three entry points and why they differ:\n *\n * - **`onEditTranslation`** — the per-row icon. The view already holds the row's persisted\n * translation and passes it, so this seeds from the argument.\n * - **`onOpenMappingEditor`** — the completeness banner's \"Open mapping editor\". The banner holds a\n * completeness item, not a mapping row, so this looks the row up and seeds from it.\n * - **`onSuggestWithAi`** — the banner's AI assist. Seeds from the model's PROPOSAL for the two\n * field maps, while keeping the row's persisted `staticRequestFields`: the advisor reasons about\n * canonical field names and has no opinion on a pinned discriminator, so overwriting it with\n * nothing would silently drop a static parameter the operator set by hand.\n *\n * @remarks\n * **Save re-issues the write with the row's EXISTING tool name and row version**, which the host\n * treats as a normal set that leaves the tool selection alone. That route is also why\n * `onMappingSaved` belongs on the mutation and not on a page's click handlers: saving field\n * translations is a save, and it was the one of the three save routes originally missed.\n */\nexport function useMappingTranslationEditor({\n service,\n connectorId,\n moduleKey,\n mappings,\n updateMapping,\n}: UseMappingTranslationEditorOptions): UseMappingTranslationEditorResult {\n const [editor, setEditor] = useState<EditorState | null>(null);\n\n // An open draft belongs to the connector and module it was opened against. Left alone, switching\n // scope with the dialog open keeps that draft on screen, and `onSave` then re-issues it with the\n // NEW row's tool name and row version — writing one connector's field translation onto another's\n // mapping, silently and with no failure. Closing is the safe resolution: the operator loses an\n // unsaved draft they are no longer looking at, rather than saving it somewhere it does not\n // belong.\n const scopeKey = `${connectorId ?? \"\"}|${moduleKey}`;\n const editorScopeRef = useRef<string>(scopeKey);\n useEffect(() => {\n if (editorScopeRef.current === scopeKey) return;\n editorScopeRef.current = scopeKey;\n setEditor(null);\n }, [scopeKey]);\n const suggest = useSuggestMappingTranslation(service);\n const test = useTestMapping(service);\n\n const onEditTranslation = useCallback(\n (operationKey: string, current: ConnectorMappingTranslationState) => {\n setEditor({ operationKey, current });\n },\n [],\n );\n\n const onCancel = useCallback(() => setEditor(null), []);\n\n const onOpenMappingEditor = useCallback(\n (operation: MappingCompletenessItem, seed?: ConnectorMappingTranslationState) => {\n const row = mappings.find((m) => m.operationKey === operation.operationKey);\n setEditor({\n operationKey: operation.operationKey,\n current:\n seed ?? {\n requestFieldMap: row?.requestFieldMap ?? null,\n responseFieldPaths: row?.responseFieldPaths ?? null,\n staticRequestFields: row?.staticRequestFields ?? null,\n },\n });\n },\n [mappings],\n );\n\n const onSuggestWithAi = useCallback(\n (operation: MappingCompletenessItem) => {\n if (!connectorId) return;\n const row = mappings.find((m) => m.operationKey === operation.operationKey);\n suggest.mutate(\n {\n connectorId,\n moduleKey,\n body: { operationKey: operation.operationKey, toolName: row?.mappedToolName ?? \"\" },\n },\n {\n onSuccess: (result) => {\n if (!result.success) return;\n onOpenMappingEditor(operation, {\n requestFieldMap: result.requestFieldMap,\n responseFieldPaths: result.responseFieldPaths,\n // Preserved, not proposed. See the remarks above.\n staticRequestFields: row?.staticRequestFields ?? null,\n });\n },\n },\n );\n },\n // `moduleKey` is a real dependency, not a module-scope constant: omitting it would capture\n // whichever module was selected when this callback was created, so a multi-module consumer that\n // switched module and then asked for a suggestion would get advice on the previous catalogue.\n [connectorId, moduleKey, mappings, suggest, onOpenMappingEditor],\n );\n\n const onSave = useCallback(\n (\n requestFieldMap: Record<string, string> | null,\n responseFieldPaths: Record<string, string> | null,\n staticRequestFields: Record<string, string> | null,\n ) => {\n if (!editor) return;\n const row = mappings.find((m) => m.operationKey === editor.operationKey);\n if (!row || !row.mappedToolName) {\n // Defensive: the editor is not reachable for an unmapped row, but a row can vanish\n // mid-edit (a concurrent delete). Bail cleanly rather than writing a mapping with no tool,\n // which the host would reject anyway and report as a failed save the operator cannot act on.\n setEditor(null);\n return;\n }\n updateMapping({\n operationKey: row.operationKey,\n toolName: row.mappedToolName,\n rowVersion: row.rowVersion ?? \"\",\n // Passed through verbatim, INCLUDING nulls. The editor delivers `null` for a side whose\n // every input the operator emptied, and that is an explicit \"clear this\" the fill-in in\n // `useConnectorMappingMutations` must not overwrite.\n requestFieldMap,\n responseFieldPaths,\n staticRequestFields,\n });\n setEditor(null);\n },\n [editor, mappings, updateMapping],\n );\n\n const onTestMapping = useCallback(\n async (request: {\n sampleArgs: Record<string, unknown>;\n requestFieldMap: Record<string, string> | null;\n responseFieldPaths: Record<string, string> | null;\n staticRequestFields?: Record<string, string> | null;\n }): Promise<TestToolMappingResult> => {\n if (!editor || !connectorId) {\n return testUnavailable(\"No connector or operation selected.\");\n }\n const row = mappings.find((m) => m.operationKey === editor.operationKey);\n if (!row?.mappedToolName) {\n return testUnavailable(\"No tool mapped to this operation.\");\n }\n return test.mutateAsync({\n connectorId,\n moduleKey,\n body: {\n operationKey: editor.operationKey,\n toolName: row.mappedToolName,\n requestFieldMap: request.requestFieldMap,\n responseFieldPaths: request.responseFieldPaths,\n // The editor's DRAFT static params, not the persisted row's, so the dry-run exercises the\n // unsaved discriminator the operator is actually testing.\n staticRequestFields: request.staticRequestFields ?? null,\n sampleArgs: request.sampleArgs,\n },\n });\n },\n // Same reason as `onSuggestWithAi`: a stale `moduleKey` here would dry-run the edit against a\n // different catalogue and report the result as if it were this one's.\n [editor, connectorId, moduleKey, mappings, test],\n );\n\n const editorRow = editor\n ? mappings.find((m) => m.operationKey === editor.operationKey)\n : undefined;\n\n return {\n editorRow,\n isOpen: editor !== null,\n onEditTranslation,\n onOpenMappingEditor,\n onSuggestWithAi,\n suggestingOperationKey: suggest.isPending\n ? (suggest.variables?.body.operationKey ?? null)\n : null,\n editorProps: {\n // Always `true`: the consumer gates the MOUNT on `isOpen`, which is what makes the editor's\n // lazy state initialisation reseed from the row each time it opens.\n open: true,\n operationLabel: editorRow?.label,\n toolName: editorRow?.mappedToolName,\n canonicalRequestFields: editorRow?.canonicalRequestFields,\n canonicalResponseFields: editorRow?.canonicalResponseFields,\n requestFieldMap: editor?.current.requestFieldMap ?? null,\n responseFieldPaths: editor?.current.responseFieldPaths ?? null,\n staticRequestFields: editor?.current.staticRequestFields ?? null,\n onSave,\n onCancel,\n onTestMapping,\n },\n };\n}\n","import { useCallback, useEffect, useRef } from \"react\";\n\nimport type { ConnectorMappingItem } from \"./types\";\n\n/** Options for {@link useAutoMatchOnConnectorSwitch}. */\nexport interface UseAutoMatchOnConnectorSwitchOptions {\n connectorId: string | undefined;\n\n /**\n * The module the rows belong to. Part of the latch key, not decoration: one connector commonly\n * serves several modules, and without this the hook cannot tell \"already auto-matched this\n * scope\" from \"auto-matched a different module on the same connector\".\n */\n moduleKey: string;\n\n /** The rows as last fetched. Their emptiness is what decides whether to trigger. */\n mappings: ConnectorMappingItem[];\n\n isLoading: boolean;\n isAutoMatching: boolean;\n\n /** `dataUpdatedAt` from `useConnectorMappings` — the fetch-freshness gate. */\n dataUpdatedAt: number;\n\n triggerAutoMatch: () => void;\n\n /**\n * `true` when the consumer arrived with a connector ALREADY selected — a `?connectorId=` deep\n * link, or a restored/preselected choice. Seeds the switch timestamp to mount time.\n *\n * @remarks\n * The three plugins this was promoted from all carry a comment saying this seed exists \"so the\n * auto-match trigger effect still fires on the first fetch\". That is backwards, and the tests\n * make it plain: seeding raises the bar the fetch timestamp has to clear, so it can only ever\n * SUPPRESS a trigger, never cause one. A fresh fetch resolves after mount and clears it either\n * way.\n *\n * What it actually does is refuse data that predates the mount. React-query can serve a cached\n * entry immediately — the operator was on this page a moment ago, or another surface primed the\n * same key — and with the ref at zero that stale snapshot is enough to fire an auto-match against\n * a connector nobody has looked at since. Seeding says \"wait for a fetch that happened after I\n * arrived\", which is the same statement `markConnectorChanged` makes about a switch. Set it\n * whenever the connector was chosen before this hook mounted.\n */\n initiallySelected?: boolean;\n}\n\n/** Return shape of {@link useAutoMatchOnConnectorSwitch}. */\nexport interface UseAutoMatchOnConnectorSwitchResult {\n /**\n * Call this whenever the consumer changes the selected connector, from inside its own setter.\n * The hook cannot observe the change itself in time: it needs the timestamp recorded BEFORE the\n * new connector's fetch resolves.\n */\n markConnectorChanged: () => void;\n}\n\n/**\n * Runs auto-match once, automatically, when a freshly selected connector turns out to have nothing\n * mapped.\n *\n * Identical in all three plugins this was promoted from, comments included, and it is entirely\n * mechanical — no identity, no routing, no persistence — so it moves whole.\n *\n * The four gates, each of which was added in response to something:\n *\n * 1. **`dataUpdatedAt > connectorChangedAt`.** Only mappings fetched AFTER the latest switch are\n * inspected. Without it the previous connector's data briefly reads as empty mid-transition and\n * auto-match fires against the wrong connector's emptiness.\n * 2. **`autoMatchedRef !== connectorId`.** Once per connector. Auto-match that legitimately matches\n * nothing would otherwise re-fire on every refetch, forever.\n * 3. **Something already mapped.** Never overwrite an operator's existing choices. A connector with\n * any mapped tool is one a human has already been through.\n * 4. **Not loading, not already matching.** Obvious, and the reason the button used to appear to do\n * nothing when pressed during a fetch.\n */\nexport function useAutoMatchOnConnectorSwitch({\n connectorId,\n moduleKey,\n mappings,\n isLoading,\n isAutoMatching,\n dataUpdatedAt,\n triggerAutoMatch,\n initiallySelected = false,\n}: UseAutoMatchOnConnectorSwitchOptions): UseAutoMatchOnConnectorSwitchResult {\n const connectorChangedAtRef = useRef<number>(initiallySelected ? Date.now() : 0);\n\n // Latched on the (connector, module) PAIR, not the connector alone. The composed surface\n // supports switching module on the same page, and a connector commonly serves several of them —\n // sales has twelve. Keyed on the connector only, auto-matching module A then switching to module\n // B on the same connector left this ref equal to that connector, so B's first auto-match was\n // suppressed and the operator saw an unmapped module with no explanation.\n const autoMatchedRef = useRef<string | null>(null);\n const scopeKey = `${connectorId ?? \"\"}|${moduleKey}`;\n\n const markConnectorChanged = useCallback(() => {\n connectorChangedAtRef.current = Date.now();\n }, []);\n\n useEffect(() => {\n if (!connectorId) return;\n if (isLoading || isAutoMatching) return;\n if (dataUpdatedAt <= connectorChangedAtRef.current) return;\n if (autoMatchedRef.current === scopeKey) return;\n if (mappings.length > 0 && mappings.some((m) => m.mappedToolName)) return;\n autoMatchedRef.current = scopeKey;\n triggerAutoMatch();\n }, [connectorId, scopeKey, mappings, isLoading, isAutoMatching, dataUpdatedAt, triggerAutoMatch]);\n\n return { markConnectorChanged };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nconst STORAGE_KEY_PREFIX = \"cc-connector-mapping-banner-dismissed\";\n\n/** Return shape of {@link useMappingBannerDismissal}. */\nexport interface UseMappingBannerDismissalResult {\n dismissed: boolean;\n dismiss: () => void;\n\n /** Un-dismisses. Call it when the selected connector changes — see the remarks. */\n reset: () => void;\n}\n\n/**\n * Dismissal of the auto-match outcome banner, per `(organisation, module)` pair, in\n * `localStorage`.\n *\n * Byte-identical in all three plugins this was promoted from, including the storage key prefix — so\n * a plugin adopting this keeps every operator's existing dismissals rather than resurfacing a\n * banner they have already dealt with.\n *\n * @param organisationId - From the consumer's own identity source. Injected rather than read here:\n * one plugin gets it from `useHostIdentity`, two from a local `useAuth`, and importing either would\n * make this hook unusable in the other. While it is falsy — pre-auth, or still resolving — the hook\n * reports a stable un-dismissed state and ignores writes, so the consumer renders the banner once\n * identity arrives instead of persisting a dismissal against no tenant.\n *\n * @param moduleKey - Scoped on the module as well as the organisation, so \"dismissed in Repo\n * Monitoring\" does not silence the banner in Timesheets.\n *\n * @remarks\n * The consumer is expected to call {@link UseMappingBannerDismissalResult.reset} when the selected\n * connector changes. Dismissal is keyed on organisation and module but NOT on connector, and a\n * dismissal earned by one connector's banner should not pre-silence a different connector's in the\n * same module. Keying on the connector instead would leak an unbounded number of `localStorage`\n * entries, one per connector the operator ever looked at.\n */\n/**\n * Reads the stored dismissal for a scope. Shared by the state initialiser and the scope-change\n * resync so the two cannot answer differently, and so the no-storage fallback is written once.\n */\nfunction readDismissed(storageKey: string | null): boolean {\n if (!storageKey) return false;\n try {\n return localStorage.getItem(storageKey) === \"true\";\n } catch {\n // Private mode, or storage disabled. The page must still work, just without memory.\n return false;\n }\n}\n\nexport function useMappingBannerDismissal(\n organisationId: string | undefined,\n moduleKey: string,\n): UseMappingBannerDismissalResult {\n const storageKey = organisationId\n ? `${STORAGE_KEY_PREFIX}:${organisationId}:${moduleKey}`\n : null;\n\n const [dismissed, setDismissed] = useState<boolean>(() => readDismissed(storageKey));\n\n // Re-read when the SCOPE changes. The initialiser above runs once per mount, so without this a\n // module or organisation switch on a page that stays mounted keeps the previous scope's answer:\n // a module dismissed earlier hides a different module's banner, and an organisation switch\n // carries one tenant's UI state into the next. Guarded on the key rather than run every render so\n // a dismissal made in THIS scope is not immediately overwritten by a re-read.\n const appliedKeyRef = useRef<string | null>(storageKey);\n useEffect(() => {\n if (appliedKeyRef.current === storageKey) return;\n appliedKeyRef.current = storageKey;\n setDismissed(readDismissed(storageKey));\n }, [storageKey]);\n\n const dismiss = useCallback(() => {\n if (!storageKey) return;\n try {\n localStorage.setItem(storageKey, \"true\");\n } catch {\n // Ignored: the banner just will not stay dismissed past this session.\n }\n setDismissed(true);\n }, [storageKey]);\n\n const reset = useCallback(() => {\n if (!storageKey) return;\n try {\n localStorage.removeItem(storageKey);\n } catch {\n // Ignored, as above.\n }\n setDismissed(false);\n }, [storageKey]);\n\n return { dismissed, dismiss, reset };\n}\n","import { useCallback, useMemo } from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\n\nimport { useAuthenticatedQuery } from \"../platform-react/useAuthenticatedQuery\";\nimport { useMutation } from \"@tanstack/react-query\";\nimport type { IConnectorMappingService } from \"./IConnectorMappingService\";\nimport { MODULE_PROVIDER_KIND, type ModuleProvider, type ModuleProviderKind } from \"./types\";\n\n/** Query key for one module's provider selection. */\nexport function moduleProviderQueryKey(moduleKey: string) {\n return [\"connector-mappings\", \"module-provider\", moduleKey] as const;\n}\n\nexport interface UseModuleProviderOptions {\n service: IConnectorMappingService;\n moduleKey: string;\n\n /**\n * Called after a successful write, with the provider as stored. Lets a page invalidate its own\n * reads — the mappings list is keyed on the connector, so a provider change is a different\n * connector's rows.\n */\n onProviderChanged?: (provider: ModuleProvider) => void;\n}\n\nexport interface UseModuleProviderResult {\n /** The organisation's stored choice, or the unconfigured state while loading or unset. */\n provider: ModuleProvider;\n\n /**\n * The chosen connector id, or `\"\"` when the module is unconfigured or served by a plugin.\n *\n * Shaped to drop straight into `useConnectorToolMappings`'s `connectorId`, which is the whole\n * point of this hook: that surface already takes the selection as an input, so adopting the\n * organisation-wide answer is a change of SOURCE and not a change to the surface.\n */\n connectorId: string;\n\n isLoading: boolean;\n\n /** Point the module at a connector. Pass `\"\"` to clear the selection back to unconfigured. */\n selectConnector: (connectorId: string) => void;\n\n /** Point the module at another installed extension. */\n selectExtension: (extensionId: string) => void;\n\n isSaving: boolean;\n error?: unknown;\n}\n\nconst UNCONFIGURED: ModuleProvider = {\n targetKind: null,\n connectorId: null,\n targetExtensionId: null,\n rowVersion: null,\n};\n\n/**\n * Reads and writes WHICH PROVIDER serves a module, as the organisation's own stored answer.\n *\n * @remarks\n * **This replaces a browser-local selection, and that is the entire point.** Before the host had\n * somewhere to put it, each plugin kept the operator's connector choice in `localStorage` — so the\n * answer was per-device rather than per-organisation. One operator selected a provider, a colleague\n * still saw the old one, and the two never reconciled. Worse, the two mechanisms could disagree\n * about a module that a plugin now serves, which is the situation the whole provider-selection\n * concept exists to end.\n *\n * **A clear is a first-class outcome.** `selectConnector(\"\")` writes the unconfigured state rather\n * than silently doing nothing, because \"no provider chosen\" and \"chose a connector with no mappings\n * yet\" are different situations with different remedies — the host deliberately does not default\n * one to the other, and neither does this.\n *\n * **The row version is handled here so callers never see it.** It is read from the query, sent on\n * the write, and refreshed from the write's response. A caller that has to thread a concurrency\n * token through its own state is a caller that will eventually send a stale one.\n */\nexport function useModuleProvider({\n service,\n moduleKey,\n onProviderChanged,\n}: UseModuleProviderOptions): UseModuleProviderResult {\n const queryClient = useQueryClient();\n const queryKey = useMemo(() => moduleProviderQueryKey(moduleKey), [moduleKey]);\n\n const query = useAuthenticatedQuery<ModuleProvider>({\n queryKey,\n queryFn: () => service.getModuleProvider(moduleKey),\n enabled: !!moduleKey,\n meta: { errorTitle: \"Error loading the module's provider\" },\n });\n\n const provider = query.data ?? UNCONFIGURED;\n\n const save = useMutation({\n mutationFn: (next: {\n targetKind: ModuleProviderKind;\n connectorId: string | null;\n targetExtensionId: string | null;\n }) =>\n service.setModuleProvider(\n moduleKey,\n next.targetKind,\n next.connectorId,\n next.targetExtensionId,\n provider.rowVersion,\n ),\n meta: { errorTitle: \"Could not change the module's provider\" },\n onSuccess: (stored) => {\n // Seeded rather than invalidated, so the refreshed row version is in place before any\n // subsequent write reads it. An invalidate-only would leave the previous token in the cache\n // until the refetch landed, and a second quick change would be rejected as stale.\n queryClient.setQueryData(queryKey, stored);\n onProviderChanged?.(stored);\n },\n });\n\n const selectConnector = useCallback(\n (connectorId: string) =>\n save.mutate({\n // An empty id is the CLEAR, and it must reach the host as one rather than being dropped\n // here: leaving the previous selection in place is exactly the \"connector is stuck forever\"\n // behaviour this surface has had to fix once already.\n targetKind: MODULE_PROVIDER_KIND.Connector,\n connectorId: connectorId || null,\n targetExtensionId: null,\n }),\n [save],\n );\n\n const selectExtension = useCallback(\n (extensionId: string) =>\n save.mutate({\n targetKind: MODULE_PROVIDER_KIND.Plugin,\n connectorId: null,\n targetExtensionId: extensionId || null,\n }),\n [save],\n );\n\n return {\n provider,\n // Only a CONNECTOR selection produces a connector id. A module served by a plugin has no\n // connector, and reporting one would make the mapping surface fetch rows for a connector the\n // operator did not choose.\n connectorId:\n provider.targetKind === MODULE_PROVIDER_KIND.Connector ? (provider.connectorId ?? \"\") : \"\",\n isLoading: query.isLoading,\n selectConnector,\n selectExtension,\n isSaving: save.isPending,\n error: query.error ?? save.error ?? undefined,\n };\n}\n","/**\n * What a canonical operation resolves to, and what happens to its field translation when that\n * changes.\n *\n * The Tool Mappings surface used to have one answer: a connector's tool. The platform now also\n * lets a mapping target another installed application's MCP tool, so \"Projects reads employees\n * from the HR app\" and \"Projects reads them from Workday\" are the same configuration with a\n * different provider. That makes the target a two-part thing - WHO provides it, and WHICH of their\n * tools - and this module owns the rules that follow from it.\n *\n * Pure by design. No react, no react-query, no transport: every rule here is a function of its\n * arguments, so the rules that were previously comments inside a mutation hook are now things a\n * test can fail.\n */\nimport {\n MAPPING_TARGET_KIND,\n MAPPING_TARGET_KIND_WIRE,\n type ConnectorMappingTranslationState,\n type MappingTargetKind,\n type MappingTargetKindWire,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Wire vocabulary conversion\n// ---------------------------------------------------------------------------\n\n/**\n * The UI-side kind as the host spells it.\n *\n * The host persists this string VERBATIM and matches it by name and case against its own\n * `ToolMappingTargetKind`, so a differently-cased value is stored as something that matches neither\n * branch of the resolver nor the database CHECK constraint. Converting in one named function is\n * what keeps that from being retyped as a literal at a call site.\n */\nexport function toWireTargetKind(kind: MappingTargetKind): MappingTargetKindWire {\n return kind === MAPPING_TARGET_KIND.plugin\n ? MAPPING_TARGET_KIND_WIRE.plugin\n : MAPPING_TARGET_KIND_WIRE.connector;\n}\n\n/**\n * The host's kind as this surface spells it, or `null`.\n *\n * `null` covers two cases on purpose, because the UI does the same thing in both: the module has no\n * selection at all (the host returns a null `targetKind` for an unconfigured module, and\n * deliberately does NOT default it to `\"Connector\"`), or the stored string matched neither member.\n * An unrecognised value is NOT guessed into a kind - rendering a choice nobody made is the exact\n * failure the host's own null is there to avoid, and a guess here would reintroduce it one layer up.\n */\nexport function fromWireTargetKind(value: string | null | undefined): MappingTargetKind | null {\n if (value === MAPPING_TARGET_KIND_WIRE.connector) return MAPPING_TARGET_KIND.connector;\n if (value === MAPPING_TARGET_KIND_WIRE.plugin) return MAPPING_TARGET_KIND.plugin;\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// The target\n// ---------------------------------------------------------------------------\n\n/**\n * Who answers a module's operations: a connector to a third-party system, or another installed\n * application.\n *\n * The plugin arm carries the providing extension rather than inferring it from the tool name's\n * prefix. That is the host's requirement and its reasoning is worth keeping here: the router\n * resolves a plugin tool by SCANNING the enabled extensions for a name match, breaking a duplicate\n * module-key collision by lowest extension id. A scan result is not a choice, and this type is\n * about recording a choice.\n */\nexport type MappingProvider =\n | { kind: \"connector\"; connectorId: string }\n | { kind: \"plugin\"; targetExtensionId: string };\n\n/**\n * A provider plus the specific tool of theirs that answers one operation.\n *\n * `toolName` means different things per arm, and both are the host's shape rather than this\n * module's invention: a connector target stores the BARE vendor verb, because the\n * `integrations:{slug}:` prefix is a function of the connector and not of the row; a plugin target\n * stores the FULLY NAMESPACED name (`hr:get-employees-by-ids`), because a plugin tool has no slug\n * to prefix and already routes verbatim.\n *\n * `\"\"` is the unmap request, exactly as on the connector-only surface.\n */\nexport type MappingTarget = MappingProvider & { toolName: string };\n\n/**\n * The stored mapping a write is replacing, reduced to the parts that decide what happens to the\n * translation.\n *\n * Both provider ids are optional because a caller assembles this from a row plus whatever it knows\n * about where the row came from - the connector-scoped list read, for instance, returns rows that\n * do not repeat the connector id the caller passed to fetch them. Absent is treated as \"not the\n * same provider\"; see {@link isSameTarget} for why that direction is the safe one.\n */\nexport interface ExistingMappingTarget extends Partial<ConnectorMappingTranslationState> {\n operationKey: string;\n targetKind: MappingTargetKind;\n\n /** The tool currently mapped, or `null` when the operation is unmapped. */\n mappedToolName: string | null;\n\n connectorId?: string | null;\n targetExtensionId?: string | null;\n}\n\n/**\n * One mapping write, with every translation member decided.\n *\n * The three translation members are REQUIRED and nullable rather than optional, matching\n * `IConnectorMappingService.updateMapping` and for the same reason: on this surface an omitted\n * member and a null member both write NULL host-side, so a type that lets a member be omitted lets\n * a caller destroy a translation without saying so.\n *\n * The two provider ids ARE optional, and exactly one of them is ever present. Absent rather than\n * null: \"there is no connector here\" and \"clear the connector\" are different statements, and only\n * one of them is true of a plugin target.\n */\nexport interface MappingUpdate extends ConnectorMappingTranslationState {\n targetKind: MappingTargetKind;\n toolName: string;\n connectorId?: string;\n targetExtensionId?: string;\n}\n\n/**\n * Does the write land on the SAME tool the stored translation was written against?\n *\n * Identity is the whole target - kind, provider, and tool name - not the tool name alone, and that\n * is deliberately stricter than a literal reading of the rule it serves. A bare vendor verb is not\n * unique: `get_file_contents` names a different tool with a different parameter shape in every\n * connector that has one, so comparing names alone would carry a translation from one vendor's tool\n * onto another's and call it unchanged.\n *\n * Unknown provenance counts as a different target. When a caller supplies no provider id on\n * {@link ExistingMappingTarget}, this returns `false` and the translation is discarded. That is the\n * safe direction of an unavoidable choice: a discarded translation is visibly absent and the\n * operator re-enters it, a carried-over one is silently wrong and nothing on the page says so.\n */\nexport function isSameTarget(\n existing: ExistingMappingTarget | null | undefined,\n next: MappingTarget,\n): boolean {\n if (!existing) return false;\n if (existing.targetKind !== next.kind) return false;\n\n // An unmapped row has no tool for a translation to have been written against, so there is nothing\n // to preserve even when the provider matches.\n if (!existing.mappedToolName) return false;\n if (existing.mappedToolName !== next.toolName) return false;\n\n return next.kind === MAPPING_TARGET_KIND.connector\n ? existing.connectorId === next.connectorId\n : existing.targetExtensionId === next.targetExtensionId;\n}\n\n/** Arguments for {@link buildMappingUpdate}. */\nexport interface BuildMappingUpdateArgs {\n /** The stored mapping, or `null` when the operation has never been mapped. */\n existing: ExistingMappingTarget | null;\n\n /** Where the operation is being pointed now. */\n next: MappingTarget;\n\n /**\n * The translation the caller is EXPLICITLY setting, member by member.\n *\n * Read with `!== undefined`, never `??`, and the difference is the whole point:\n *\n * - a member left `undefined` is the caller not talking about it, and it is decided by the\n * target-change rule below;\n * - a member set to `null` is the caller SAYING clear it, and it survives untouched.\n *\n * `??` collapses those two into one, which is the defect this surface has shipped three times -\n * once as \"picking a tool wiped the operator's field mapping\", once as \"the editor could not\n * clear a side at all\", and once in each of three plugin repos.\n */\n translation?: Partial<ConnectorMappingTranslationState>;\n}\n\n/**\n * Builds the write for pointing one operation at a target, deciding what happens to its translation.\n *\n * Two rules, and they are separate because they answer different questions:\n *\n * 1. **A translation belongs to ONE tool.** When the target changes, `requestFieldMap`,\n * `responseFieldPaths` and `staticRequestFields` are DISCARDED rather than carried across. They\n * are a map onto one tool's parameter names, and against a different tool they do not degrade -\n * they silently mean something else. Switching provider necessarily changes the tool, so this\n * fires on every provider switch and not only on a tool edit within a provider.\n *\n * 2. **Omitted is not cleared.** Whatever the caller states in `translation` wins, including an\n * explicit `null`. An operator supplying a translation for the new tool in the same action is\n * not fighting rule 1; rule 1 exists because nobody said anything about the translation, and\n * here somebody did.\n *\n * The order matters and is asserted by the tests: an explicit member beats the discard, and the\n * discard beats the carry-forward.\n */\nexport function buildMappingUpdate({\n existing,\n next,\n translation,\n}: BuildMappingUpdateArgs): MappingUpdate {\n const sameTarget = isSameTarget(existing, next);\n\n const decide = (\n member: keyof ConnectorMappingTranslationState,\n ): Record<string, string> | null => {\n const supplied = translation?.[member];\n // Rule 2. `!== undefined`, never `??`: `null` here is an instruction, not an absence.\n if (supplied !== undefined) return supplied;\n\n // Rule 1. A different tool means the stored translation describes something else.\n if (!sameTarget) return null;\n\n return existing?.[member] ?? null;\n };\n\n const update: MappingUpdate = {\n targetKind: next.kind,\n toolName: next.toolName,\n requestFieldMap: decide(\"requestFieldMap\"),\n responseFieldPaths: decide(\"responseFieldPaths\"),\n staticRequestFields: decide(\"staticRequestFields\"),\n };\n\n // Exactly one provider id, and the other key is ABSENT rather than null. The host's write\n // commands are split per target kind and each refuses a payload carrying the other half, so\n // sending `connectorId: null` on a plugin write is not a tidier way of saying nothing - it is a\n // field the endpoint has no slot for.\n if (next.kind === MAPPING_TARGET_KIND.connector) {\n update.connectorId = next.connectorId;\n } else {\n update.targetExtensionId = next.targetExtensionId;\n }\n\n return update;\n}\n\n/**\n * The provider half of a target, for the per-module selection. Drops the tool, because the module\n * selection is a choice about WHO serves the module and every operation under it keeps its own tool.\n */\nexport function providerOf(target: MappingTarget): MappingProvider {\n return target.kind === MAPPING_TARGET_KIND.connector\n ? { kind: MAPPING_TARGET_KIND.connector, connectorId: target.connectorId }\n : { kind: MAPPING_TARGET_KIND.plugin, targetExtensionId: target.targetExtensionId };\n}\n\n/**\n * The provider's id, whichever half it is. For a caller that only needs to know whether the choice\n * changed, rather than which kind it is.\n */\nexport function providerIdOf(provider: MappingProvider): string {\n return provider.kind === MAPPING_TARGET_KIND.connector\n ? provider.connectorId\n : provider.targetExtensionId;\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type { MappingCompletenessBannerProps } from \"../components/connectors/MappingCompletenessBanner\";\nimport type { ConnectorMappingTranslationEditorProps } from \"../components/connectors/ConnectorMappingTranslationEditorView\";\nimport type { MappingReadiness } from \"../components/connectors/mappingCompleteness\";\nimport type { MappingCompletenessItem } from \"../components/connectors/types\";\nimport type { ConnectorOption, ConnectorToolOption } from \"./connectorOptions\";\nimport type { IConnectorMappingService } from \"./IConnectorMappingService\";\nimport { fromWireTargetKind } from \"./targetSelection\";\nimport { useModuleProvider } from \"./useModuleProvider\";\nimport { useAutoMatchOnConnectorSwitch } from \"./useAutoMatchOnConnectorSwitch\";\nimport {\n useConnectorMappingCompleteness,\n useConnectorMappings,\n} from \"./useConnectorMappings\";\nimport { useConnectorMappingMutations } from \"./useConnectorMappingMutations\";\nimport { useMappingBannerDismissal } from \"./useMappingBannerDismissal\";\nimport { useMappingTranslationEditor } from \"./useMappingTranslationEditor\";\nimport {\n MAPPING_TARGET_KIND,\n type ConnectorMappingItem,\n type ConnectorMappingTranslationState,\n type MappingTargetKind,\n type PluginProviderCandidate,\n} from \"./types\";\n\n/** Options for {@link useConnectorToolMappings}. */\nexport interface UseConnectorToolMappingsOptions {\n /** Mapping transport. See `ConnectorMappingService` for the default implementation. */\n service: IConnectorMappingService;\n\n // --- the selected pair -----------------------------------------------------\n\n /** `\"\"` or `undefined` before the operator has chosen one. */\n connectorId: string | undefined;\n\n /**\n * Selected module. A plain input, so a consumer with ONE catalogue passes a module-scope constant\n * and a consumer with twelve passes whichever the operator selected — every query key, the\n * invalidation, the auto-match trigger and the banner dismissal re-key with it.\n */\n moduleKey: string;\n\n /** Human label for the module, for the view's own header and empty state. */\n moduleLabel: string;\n\n // --- data the consumer owns ------------------------------------------------\n\n /**\n * The connectors to offer. Injected because connector enumeration differs genuinely between\n * consumers — one reads `settings:list-connectors`, one pages a full connector CRUD service, one\n * has no source yet — and none of those belongs in a mapping hook. `mapConnectorsToOptions`\n * projects the default service's shape.\n */\n connectors: ConnectorOption[];\n\n /**\n * Whether the CONNECTORS query is still in flight. Folded into the view's `isLoading` because the\n * mappings query is disabled until a connector is chosen and therefore reports `isLoading: false`\n * on first load — which made the view skip its skeleton and render the picker as an EMPTY\n * dropdown while the connector list was still loading. It read as \"this organisation has no\n * connectors\", and then they appeared.\n */\n isConnectorsLoading?: boolean;\n\n /** The selected connector's mappable tools. `mapConnectorToolsToOptions` projects them. */\n availableTools: ConnectorToolOption[];\n\n /**\n * The consumer's connector setter — bare `setState`, a persisting setter, a URL write, whatever\n * it owns. Wrapped here so the auto-match trigger learns of the switch; the consumer wires the\n * returned `view.onConnectorChange` to its picker and never calls its own setter directly.\n */\n onConnectorChange: (connectorId: string) => void;\n\n /** `true` when a connector was already selected on mount (deep link / restored / preselected). */\n initiallySelected?: boolean;\n\n // --- provider selection ----------------------------------------------------\n //\n // All optional. Omit them and this hook behaves exactly as it did when a connector was the only\n // thing that could serve a module: no extra query, no extra view props, nothing rendered.\n\n /**\n * Opts this hook into the per-module provider choice: it reads and writes the organisation's\n * stored provider through {@link useModuleProvider} on the `service` already passed above, and\n * hands the view the picker's props.\n *\n * Defaults to `false`, and the default is the load-bearing part. The transport exists -\n * `connectors:mappings:get-provider` and `connectors:mappings:set-provider` are real tools - but\n * only on a host that has them DEPLOYED, and this package is pinned independently by every\n * plugin. Turning the read on for every existing consumer would fire a tool call an older host\n * answers with an error toast, on a surface that worked. A consumer opts in when its host is\n * ready, and adds the two tool names to its `useToolInvokerMap`.\n */\n providerSelectionEnabled?: boolean;\n\n /**\n * The installed applications on offer, with the tools each can answer this module's operations\n * with.\n *\n * An INPUT rather than something this hook fetches, matching {@link connectors} and for the same\n * reason: the candidate set is a server-side authorisation decision (see\n * {@link PluginProviderCandidate}), and no consumer should be able to widen it by filtering\n * client-side. Supply it from whatever surface the host eventually exposes - there is no\n * candidate-list endpoint in any transport today, which is why this cannot be fetched here.\n *\n * Left empty, the app arm of the picker has nothing to offer, so the view renders the connector\n * arm only.\n */\n pluginProviders?: PluginProviderCandidate[];\n\n /** Whether the candidate list is still in flight. Folded into the view's `isLoading`. */\n isPluginProvidersLoading?: boolean;\n\n // --- injected environment --------------------------------------------------\n\n /**\n * Current organisation, for the per-tenant banner dismissal. Injected because identity differs:\n * one plugin reads `useHostIdentity`, two read a local `useAuth`. Absent is handled — dismissal\n * simply does not persist until identity resolves.\n */\n organisationId?: string;\n\n /** Href of the host's AI-providers settings page. Routing is never owned here. */\n aiProvidersHref: string;\n\n /** Href of the selected connector's settings page, already scoped to `connectorId`. */\n connectorSettingsHref: string;\n\n /** Passed through to the view. See `ConnectorMappingViewProps.withHeader`. */\n withHeader?: boolean;\n\n /**\n * Called after any save. See `useConnectorMappingMutations` — this is the hook a consumer needs\n * in order to mirror the connector into its own settings, without which a background worker\n * cannot find a connector at all.\n */\n onMappingSaved?: (connectorId: string, moduleKey: string) => void;\n\n /** Success-toast sink. Defaults to the SDK's host-event toast. */\n showSuccessToast?: (title: string, description?: string) => void;\n}\n\n/**\n * Props for `ConnectorMappingView`, minus nothing — spread this straight onto it.\n *\n * Declared structurally rather than imported from the component so this module stays free of\n * `@mui/material`: a consumer can take the hooks without installing MUI, and the view's own props\n * type is assignable to this.\n */\nexport interface ConnectorMappingViewBundle {\n isLoading: boolean;\n moduleKey: string;\n moduleLabel: string;\n withHeader?: boolean;\n connectors: ConnectorOption[];\n selectedConnectorId: string;\n onConnectorChange: (connectorId: string) => void;\n\n // Present only when the consumer set `providerSelectionEnabled`; absent otherwise, which is what\n // keeps the view rendering its pre-provider shape for an un-adopted consumer.\n providerKind?: MappingTargetKind;\n onProviderKindChange?: (kind: MappingTargetKind) => void;\n pluginProviders?: { extensionId: string; name: string }[];\n selectedPluginExtensionId?: string;\n onPluginProviderChange?: (extensionId: string) => void;\n\n mappings: ConnectorMappingItem[];\n availableTools: ConnectorToolOption[];\n onUpdateMapping: (operationKey: string, toolName: string, rowVersion: string) => void;\n onAutoMatch: () => void;\n isUpdating: boolean;\n isAutoMatching: boolean;\n bannerState: ReturnType<typeof useConnectorMappingMutations>[\"autoMatchSummary\"];\n onDismissBanner: () => void;\n aiProvidersHref: string;\n connectorSettingsHref: string;\n onEditTranslation: (operationKey: string, current: ConnectorMappingTranslationState) => void;\n}\n\n/** Return shape of {@link useConnectorToolMappings}. */\nexport interface UseConnectorToolMappingsResult {\n /** Spread onto `ConnectorMappingView`. */\n view: ConnectorMappingViewBundle;\n\n /**\n * Spread onto `MappingCompletenessBanner`.\n *\n * Render it whenever `banner.isProviderUnconfigured` is true, and otherwise only when a provider\n * is selected. Gating the mount on a selected CONNECTOR - which the pre-provider guidance here\n * said, because a connector was the only provider there was - would hide the one message that\n * tells an operator how to select one.\n */\n banner: MappingCompletenessBannerProps;\n\n /** Spread onto `ConnectorMappingTranslationEditorView`. Mount it only when `isEditorOpen`. */\n editor: ConnectorMappingTranslationEditorProps;\n\n /** Gate the editor dialog's MOUNT on this, so it reseeds from the row each time it opens. */\n isEditorOpen: boolean;\n\n /** The row the editor is open for, or `undefined`. Exposed for a consumer's own render guards. */\n editorRow: ConnectorMappingItem | undefined;\n\n /** The rows, for a consumer that renders something of its own alongside the table. */\n mappings: ConnectorMappingItem[];\n\n /** Per-operation completeness, for a consumer that wants it outside the banner. */\n completenessOperations: MappingCompletenessItem[];\n\n /** Readiness over REQUIRED operations only — for a progress indicator or a \"ready\" badge. */\n readiness: MappingReadiness;\n\n /** True while either query is loading, connectors included. */\n isLoading: boolean;\n}\n\n/**\n * The whole Tool Mappings surface for one connector + module, assembled from the parts in this\n * module.\n *\n * ## What this is, and what it deliberately is not\n *\n * Three plugins each had a ~300-line page hook, and roughly 85% of each was identical. This is that\n * 85%: the reads, the writes, the translation editor, the auto-match trigger, the banner dismissal,\n * and the three prop bundles a page spreads. What is NOT here is the 15% that genuinely differs —\n * **which** connector and module are selected, where the selection is remembered, where identity\n * comes from, how hrefs are built, and how connectors are enumerated. Those arrive as inputs.\n *\n * That split is not a compromise, it is the finding: every one of those five is a real difference\n * between the three consumers, and every attempt to absorb one would have imported a plugin's\n * router, auth module or connector service into this package. A consumer's remaining page hook is\n * the selection logic and nothing else.\n *\n * ## Multi-module is not a special case\n *\n * `moduleKey` and `moduleLabel` are ordinary inputs, so a page with a module selector re-renders\n * this hook with the new key and everything downstream re-keys — including the auto-match trigger,\n * which will then evaluate the new module's emptiness rather than the old one's. A page with one\n * module passes a constant and pays nothing.\n *\n * @example Single-module consumer\n * ```ts\n * const surface = useConnectorToolMappings({\n * service,\n * connectorId,\n * moduleKey: MODULE_KEY,\n * moduleLabel: \"Repo Monitoring\",\n * connectors: mapConnectorsToOptions(connectors),\n * isConnectorsLoading,\n * availableTools: mapConnectorToolsToOptions(connectorDetail?.tools ?? []),\n * onConnectorChange: setConnectorId,\n * organisationId: user?.organisationId,\n * aiProvidersHref: routes.settings.aiProviders.root(),\n * connectorSettingsHref: connectorId\n * ? routes.settings.connectors.details(connectorId)\n * : routes.settings.connectors.root(),\n * });\n * ```\n */\nexport function useConnectorToolMappings({\n service,\n connectorId,\n moduleKey,\n moduleLabel,\n connectors,\n isConnectorsLoading = false,\n availableTools,\n onConnectorChange,\n initiallySelected = false,\n organisationId,\n aiProvidersHref,\n connectorSettingsHref,\n withHeader,\n onMappingSaved,\n showSuccessToast,\n providerSelectionEnabled = false,\n pluginProviders,\n isPluginProvidersLoading = false,\n}: UseConnectorToolMappingsOptions): UseConnectorToolMappingsResult {\n // Gated by passing an empty module key, which is the hook's own documented \"nothing to read\"\n // condition (`enabled: !!moduleKey`), rather than by a second switch on its options. A hook\n // cannot be called conditionally, and adding a parameter to a surface that shipped days ago\n // purely to say what an existing one already says would be two ways to express one state.\n const {\n provider: storedProvider,\n connectorId: storedConnectorId,\n isLoading: isProviderLoading,\n selectConnector,\n selectExtension,\n } = useModuleProvider({\n service,\n moduleKey: providerSelectionEnabled ? moduleKey : \"\",\n });\n\n // The kind the operator has picked but not yet completed, because a kind alone is half a choice\n // and the host refuses to store one: a connector choice must name a connector, a plugin choice an\n // extension. So switching the kind is local intent, and the write happens when they pick the\n // concrete provider under it. Without this the \"Served by\" control would appear not to respond.\n const [pendingKind, setPendingKind] = useState<MappingTargetKind | null>(null);\n\n // Intent belongs to the module it was expressed for. A multi-module page that switched module\n // would otherwise carry the previous module's half-made choice onto a module the operator has not\n // looked at, and the first provider they picked would be stored against the wrong one.\n const pendingKindModuleRef = useRef(moduleKey);\n if (pendingKindModuleRef.current !== moduleKey) {\n pendingKindModuleRef.current = moduleKey;\n if (pendingKind !== null) setPendingKind(null);\n }\n\n // The stored kind, converted from the host's vocabulary rather than compared against it. See\n // `fromWireTargetKind`: an unrecognised value becomes `null`, never a guessed kind.\n const storedKind = fromWireTargetKind(storedProvider.targetKind);\n const providerKind = providerSelectionEnabled\n ? (pendingKind ?? storedKind ?? undefined)\n : undefined;\n const isPluginMode = providerKind === MAPPING_TARGET_KIND.plugin;\n\n const selectedPluginExtensionId =\n storedKind === MAPPING_TARGET_KIND.plugin ? (storedProvider.targetExtensionId ?? \"\") : \"\";\n\n // With provider selection on, the ORGANISATION's stored answer wins over the id the consumer\n // passed. That is the whole point of the stored selection: the `connectorId` prop is fed from a\n // per-device source in every consumer today, so preferring it here would leave two answers to one\n // question and no rule for which is right. `storedConnectorId` is `\"\"` unless the stored kind\n // is Connector, so a plugin-served module contributes nothing to this line.\n const effectiveConnectorId = providerSelectionEnabled ? storedConnectorId : connectorId;\n\n // `\"\"` and `undefined` both mean \"nothing selected\"; normalise once so every hook below and the\n // view agree. Two of the three plugins carried `connectorId || undefined` at four call sites.\n //\n // Forced to undefined in plugin mode, which disables every connector-scoped read. Leaving the\n // previous connector's id in place would render THAT connector's mapping rows and completeness\n // under a module the operator has just pointed at an app - data that is real, and about something\n // else.\n const selectedConnectorId = isPluginMode ? undefined : effectiveConnectorId || undefined;\n const scope = { service, connectorId: selectedConnectorId, moduleKey };\n\n const { mappings, isLoading, dataUpdatedAt } = useConnectorMappings(scope);\n const { operations: completenessOperations, readiness } =\n useConnectorMappingCompleteness(scope);\n\n const { triggerAutoMatch, isAutoMatching, updateMapping, isUpdating, autoMatchSummary } =\n useConnectorMappingMutations({ ...scope, onMappingSaved, showSuccessToast });\n\n const editor = useMappingTranslationEditor({\n service,\n connectorId: selectedConnectorId,\n moduleKey,\n mappings,\n updateMapping,\n });\n\n const { dismissed, dismiss, reset } = useMappingBannerDismissal(organisationId, moduleKey);\n\n const { markConnectorChanged } = useAutoMatchOnConnectorSwitch({\n connectorId: selectedConnectorId,\n moduleKey,\n mappings,\n isLoading,\n isAutoMatching,\n dataUpdatedAt,\n triggerAutoMatch,\n initiallySelected,\n });\n\n const handleProviderKindChange = useCallback(\n (kind: MappingTargetKind) => {\n setPendingKind(kind);\n },\n [],\n );\n\n const handlePluginProviderChange = useCallback(\n (extensionId: string) => {\n selectExtension(extensionId);\n },\n [selectExtension],\n );\n\n const handleConnectorChange = useCallback(\n (id: string) => {\n onConnectorChange(id);\n markConnectorChanged();\n\n // Choosing a connector IS the connector half of the provider choice, so it is recorded here\n // rather than behind a second confirm. A separate \"save provider\" step would let an operator\n // pick a connector, map every operation against it, and still have the module resolve as\n // unconfigured - the mappings correct and the page inert.\n //\n // An empty id is passed through as the CLEAR, not swallowed: `selectConnector(\"\")` writes the\n // unconfigured state, which is the route back that this surface has already had to add once.\n if (providerSelectionEnabled) selectConnector(id);\n },\n // `onConnectorChange` is a real dependency: a consumer that persists per organisation rebinds\n // its setter once identity resolves, and an empty dep array would capture the pre-identity one\n // and stop writing through.\n [onConnectorChange, markConnectorChanged, providerSelectionEnabled, selectConnector],\n );\n\n const handleUpdateMapping = useCallback(\n (operationKey: string, toolName: string, rowVersion: string) => {\n if (!selectedConnectorId) return;\n // Note what is NOT passed: the three translation members. Omitting them is what tells\n // `useConnectorMappingMutations` to carry the row's existing translation forward. Passing\n // explicit nulls here is the production defect this module was extracted to end.\n updateMapping({ operationKey, toolName, rowVersion });\n },\n [selectedConnectorId, updateMapping],\n );\n\n const handleAutoMatch = useCallback(() => {\n if (!selectedConnectorId) return;\n triggerAutoMatch();\n }, [selectedConnectorId, triggerAutoMatch]);\n\n // A dismissal earned by one connector's banner must not pre-silence a different connector's in\n // the same module. Dismissal is keyed on (organisation, module) only — see the hook's remarks for\n // why adding the connector to the key would be worse.\n //\n // Guarded on an ACTUAL connector change, for two reasons that are both bugs when it is not:\n //\n // * It ran on MOUNT. `useMappingBannerDismissal` reads `true` from storage in its initialiser,\n // and this effect then immediately removed the entry and flipped it back to `false` — so a\n // dismissal never survived a reload or a navigation, and the stored one was destroyed on the\n // way. The banner came back every time, which reads as the dismiss button being broken.\n // * `reset` changes identity when the dismissal's own (organisation, module) key changes, so a\n // MODULE switch re-ran it and cleared the dismissal belonging to the module just switched TO —\n // defeating the resync that hook now does. Hence the module is compared too: when it changed,\n // the dismissal is a different scope's and is re-read rather than reset.\n const previousScopeRef = useRef<{ connectorId?: string; moduleKey: string }>({\n connectorId: selectedConnectorId || undefined,\n moduleKey,\n });\n useEffect(() => {\n const previous = previousScopeRef.current;\n const current = { connectorId: selectedConnectorId || undefined, moduleKey };\n previousScopeRef.current = current;\n\n if (previous.moduleKey !== current.moduleKey) return;\n if (previous.connectorId === current.connectorId) return;\n\n reset();\n }, [selectedConnectorId, moduleKey, reset]);\n\n // In plugin mode the tools on offer are the CHOSEN app's, not the connector's. Derived here\n // rather than asked of the consumer so a page cannot supply one provider's tools against another\n // provider's operations - a picker full of names that resolve to nothing.\n const effectiveAvailableTools = useMemo<ConnectorToolOption[]>(() => {\n if (!isPluginMode) return availableTools;\n const chosen = (pluginProviders ?? []).find(\n (p) => p.extensionId === selectedPluginExtensionId,\n );\n return (chosen?.tools ?? []).map((t) => ({\n name: t.toolName,\n description: t.description ?? \"\",\n }));\n }, [isPluginMode, availableTools, pluginProviders, selectedPluginExtensionId]);\n\n const providerOptions = useMemo(\n () => (pluginProviders ?? []).map((p) => ({ extensionId: p.extensionId, name: p.name })),\n [pluginProviders],\n );\n\n const surfaceIsLoading =\n isLoading || isConnectorsLoading || isProviderLoading || isPluginProvidersLoading;\n\n return {\n view: {\n isLoading: surfaceIsLoading,\n moduleKey,\n moduleLabel,\n withHeader,\n connectors,\n selectedConnectorId: selectedConnectorId ?? \"\",\n onConnectorChange: handleConnectorChange,\n // Spread conditionally: for a consumer that has not opted in these keys are ABSENT, not\n // undefined, so the view's `providerPickerEnabled` check sees exactly what a pre-provider\n // consumer passes.\n ...(providerSelectionEnabled\n ? {\n providerKind,\n onProviderKindChange: handleProviderKindChange,\n pluginProviders: providerOptions,\n selectedPluginExtensionId,\n onPluginProviderChange: handlePluginProviderChange,\n }\n : {}),\n mappings,\n availableTools: effectiveAvailableTools,\n onUpdateMapping: handleUpdateMapping,\n onAutoMatch: handleAutoMatch,\n isUpdating,\n isAutoMatching,\n bannerState: dismissed ? null : autoMatchSummary,\n onDismissBanner: dismiss,\n aiProvidersHref,\n connectorSettingsHref,\n onEditTranslation: editor.onEditTranslation,\n },\n banner: {\n operations: completenessOperations,\n onSuggestWithAi: editor.onSuggestWithAi,\n onOpenMappingEditor: editor.onOpenMappingEditor,\n suggestingOperationKey: editor.suggestingOperationKey,\n // False whenever the consumer has not opted in, not merely when a choice exists. A page with\n // no provider control on it must not be told it has failed to use one. Also false while the\n // read is in flight, so the banner does not accuse the operator during a page load.\n isProviderUnconfigured:\n providerSelectionEnabled && !isProviderLoading && storedKind === null,\n },\n editor: editor.editorProps,\n isEditorOpen: editor.isOpen,\n editorRow: editor.editorRow,\n mappings,\n completenessOperations,\n readiness,\n isLoading: surfaceIsLoading,\n };\n}\n","import type { ConnectorSummary, ConnectorToolSummary } from \"./types\";\n\n/** A connector as the mapping picker renders it. */\nexport interface ConnectorOption {\n id: string;\n name: string;\n}\n\n/** A tool as the per-row mapping autocomplete renders it. */\nexport interface ConnectorToolOption {\n name: string;\n description: string;\n}\n\n/**\n * Project the organisation's connectors down to the picker's option shape.\n *\n * @remarks\n * **Deliberately unfiltered.** One plugin filtered this list to MCP connectors, because its\n * connector service returned every type; another reads `settings:list-connectors`, whose projection\n * does not carry a connector type at all, so the same filter compared against `undefined` and hid\n * every connector — a picker that is empty for a reason no operator could work out. The host\n * already scopes the list to the current organisation, and a mapping against a connector that\n * cannot serve it fails loudly at Test. A consumer whose own service returns types it must exclude\n * should filter BEFORE calling this, where it can name what it is excluding and why.\n */\nexport function mapConnectorsToOptions(\n connectors: readonly ConnectorSummary[],\n): ConnectorOption[] {\n return connectors.map((c) => ({ id: c.id, name: c.name }));\n}\n\n/**\n * Project a connector's tools down to the mapping autocomplete's option shape.\n *\n * Disabled tools are dropped: an operator can only map an operation to a tool the connector is\n * currently exposing, and offering a switched-off one produces a mapping that resolves to nothing\n * at runtime. The service returns them unfiltered on purpose — see\n * `ConnectorMappingService.listConnectorTools` — so the distinction stays visible one layer down.\n */\nexport function mapConnectorToolsToOptions(\n tools: readonly ConnectorToolSummary[],\n): ConnectorToolOption[] {\n return tools\n .filter((t) => t.isEnabled)\n .map((t) => ({ name: t.toolName, description: t.description ?? \"\" }));\n}\n","/**\n * The words the provider controls use, in one place, because two surfaces have to say the same ones.\n *\n * ## Why these are constants and not literals at each site\n *\n * A \"not configured\" message has to name the control that resolves it, in that control's own words.\n * This surface has already paid for the alternative: the completeness banner described a gap in the\n * abstract (\"the mapping editor\"), an operator could not find any such thing on the page, and the\n * report that came back was \"it doesnt really suggest how to fix it\". The fix was to name the\n * affordance verbatim - and a verbatim name held as two separate string literals is one rename away\n * from pointing at nothing again, with both files still compiling and both tests still green.\n *\n * So the control renders {@link PROVIDER_CONTROL_LABEL} and the banner quotes it, from here. A test\n * asserts the banner's text contains the label the view actually renders; it fails if either side\n * is edited alone.\n *\n * Pure strings, no imports. That matters: the mapping HOOKS module value-imports this, and it must\n * not drag `@mui/material` into a barrel whose consumers may not have installed it.\n */\n\n/** Label of the control that chooses what serves a module. */\nexport const PROVIDER_CONTROL_LABEL = \"Served by\";\n\n/**\n * Labels of the two things that can serve a module.\n *\n * Phrased from the operator's side rather than the schema's. \"Plugin\" and \"Connector\" are the\n * platform's words for its own internals; an operator choosing where a module's data comes from is\n * choosing between the applications they already run and something outside.\n */\nexport const PROVIDER_OPTION_LABEL = {\n plugin: \"This organisation's apps\",\n connector: \"External system\",\n} as const;\n\n/** Label of the control that picks WHICH installed application, once \"this organisation's apps\" is chosen. */\nexport const PLUGIN_PROVIDER_CONTROL_LABEL = \"App\";\n\n/** Label of the control that picks WHICH connector, once \"external system\" is chosen. */\nexport const CONNECTOR_CONTROL_LABEL = \"Connector\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/connector-mappings/ConnectorMappingService.ts","../../src/components/connectors/mappingQueryKeys.ts","../../src/components/connectors/mappingCompleteness.ts","../../src/platform-react/useAuthenticatedQuery.ts","../../src/connector-mappings/useConnectorMappings.ts","../../src/components/shared/toast.ts","../../src/connector-mappings/types.ts","../../src/connector-mappings/useConnectorMappingMutations.ts","../../src/connector-mappings/useMappingAdvisors.ts","../../src/connector-mappings/useMappingTranslationEditor.ts","../../src/connector-mappings/useAutoMatchOnConnectorSwitch.ts","../../src/connector-mappings/useMappingBannerDismissal.ts","../../src/connector-mappings/useModuleProvider.ts","../../src/connector-mappings/targetSelection.ts","../../src/connector-mappings/useConnectorToolMappings.ts","../../src/connector-mappings/connectorOptions.ts","../../src/components/connectors/providerLabels.ts"],"names":["showSuccessToast","showWarningToast","useMutation","useState","useRef","useEffect","useCallback","useQueryClient","useMemo"],"mappings":"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","file":"index.js","sourcesContent":["import type { ToolInvoker } from \"../platform-react/mcpService\";\nimport type {\n IConnectorEnumerationService,\n IConnectorMappingService,\n} from \"./IConnectorMappingService\";\nimport type {\n AutoMatchResponse,\n ConnectorMappingItem,\n ConnectorMappingSuggestionResult,\n ConnectorSummary,\n ConnectorToolSummary,\n MappingCompletenessResult,\n ModuleProvider,\n ModuleProviderKind,\n PluginProviderCandidate,\n PluginTargetMappingItem,\n SetPluginTargetMappingArgs,\n SetPluginTargetMappingResult,\n SuggestMappingTranslationBody,\n TestMappingBody,\n TestToolMappingResult,\n} from \"./types\";\n\n/** Response envelope for `connectors:mappings:list`. */\ninterface ListMappingsResponse {\n items?: ConnectorMappingItem[];\n}\n\n/** Response for `connectors:mappings:set`: the replacement concurrency token. */\ninterface SetMappingResponse {\n rowVersion?: string | null;\n findings?: MappingVerificationFinding[] | null;\n}\n\n/**\n * What the save-time verification gate could establish about the mapping that was stored.\n *\n * Rides on a SUCCESS response: the gate refuses only what the invocation path refuses on every\n * call, and everything else it establishes is true and worth an operator seeing without standing\n * in the way of a save.\n */\nexport interface MappingVerificationFinding {\n /** Stable code, e.g. `CON_MAP_020`. */\n code: string;\n /** `Warning`, or `Refusal` for one the caller acknowledged. */\n severity: string;\n /** Short operator-facing summary. */\n message: string;\n /** The longer explanation, when there is one. */\n detail?: string | null;\n}\n\n/** What a mapping save returns: the refreshed token, and anything the gate had to say. */\nexport interface MappingSaveOutcome {\n rowVersion: string | null;\n findings: MappingVerificationFinding[];\n}\n\n/**\n * Refuses a plugin-target call the host cannot currently serve, naming the layer below.\n *\n * The message carries the missing transport rather than a generic failure, because the layer that\n * knows WHY is this one and a caller that receives \"request failed\" has to guess. `detail` names\n * what exists and what does not, so the fix is legible from the message without reading this file.\n */\nfunction pluginTargetSurfaceUnavailable(method: string, detail: string): never {\n throw new Error(\n `ConnectorMappingService.${method} is not available. ${detail} ` +\n \"Subclass this service with your own implementation, or leave the plugin-target surface \" +\n \"unmounted (isPluginTargetSurfaceAvailable is false on this implementation).\",\n );\n}\n\n/**\n * The connector tool-mapping surface, served entirely by the host's `connectors:mappings:*` tools.\n *\n * ## Why the host store and not a plugin table\n *\n * A plugin resolves its operations at runtime through the platform's `resolve-mapping`, so the rows\n * have to live in the platform store. Three plugins each owned their mappings in their own schema\n * first, and every platform-side tool was blind to them: Super Admin could not inspect or repair\n * them, and platform mapping completeness did not cover them.\n *\n * ## Transport is injected\n *\n * The constructor takes a {@link ToolInvoker} — the same shape `BaseMcpService` takes — rather than\n * importing one. That keeps this class free of `@ethisyscore/extension-runtime`, so it is unit\n * testable with a plain function, and it is why this does not extend `BaseMcpService`: the base\n * class exists to give a lifted REST service a `this.tool(...)`, and there is no REST ancestry here\n * to accommodate. A plugin constructs it exactly as before:\n *\n * ```ts\n * const invoker = useToolInvokerMap(CONNECTOR_MAPPING_TOOLS);\n * const service = useMemo(() => new ConnectorMappingService(invoker), [invoker]);\n * ```\n *\n * ## Row versions are opaque\n *\n * The host declares them as `byte[]`, which crosses the wire as base64. Read one from\n * {@link getMappings} and hand the same string back on write; nothing on this side parses or\n * constructs one.\n *\n * ## Translation is NOT a patch on this surface\n *\n * The host writes NULL for a null member, so every save must carry the complete translation.\n * Sending only the field being changed silently clears the other two — that defect reached\n * production and had to be fixed three times, once per plugin, which is the reason this class is\n * here. `useConnectorMappingMutations` is where completeness is enforced; this class sends what it\n * is given, and does not paper over a partial write.\n *\n * ## Every read is null-tolerant\n *\n * Promoted from the three copies, taking the most hardened behaviour of each. A `Result` with no\n * value is representable on the wire, so `response.items` on an absent payload throws inside a\n * react-query `queryFn` or, worse, inside an `onSuccess` where the rejection is unhandled. A page\n * that reports nothing beats a page that crashed.\n */\nexport class ConnectorMappingService\n implements IConnectorMappingService, IConnectorEnumerationService\n{\n constructor(private readonly invoker: ToolInvoker) {}\n\n /** Single seam every method goes through, so a subclass can wrap logging or retries in one place. */\n protected async tool<TRes>(name: string, args: unknown = {}): Promise<TRes> {\n return (await this.invoker(name, args)) as TRes;\n }\n\n async getMappings(connectorId: string, moduleKey: string): Promise<ConnectorMappingItem[]> {\n // The `{ items }` envelope is destructured explicitly rather than assumed to be the array.\n // Reading a bare array off it is a defect that actually shipped: the table rendered its column\n // headers with no rows and raised no error.\n const response = await this.tool<ListMappingsResponse | null>(\"connectors:mappings:list\", {\n connectorId,\n moduleKey,\n });\n\n return response?.items ?? [];\n }\n\n async updateMapping(\n connectorId: string,\n moduleKey: string,\n operationKey: string,\n toolName: string,\n rowVersion: string,\n // REQUIRED, and nullable rather than optional. A write here replaces the whole translation, so\n // an omitted argument is indistinguishable from a deliberate clear — which is precisely the\n // defect this package exists to fix, reachable by calling the exported service directly and\n // bypassing the resolver in `useConnectorMappingMutations`. Making the caller pass `null` costs\n // one word and makes \"I am clearing this\" a thing they had to say.\n requestFieldMap: Record<string, string> | null,\n responseFieldPaths: Record<string, string> | null,\n staticRequestFields: Record<string, string> | null,\n ): Promise<MappingSaveOutcome> {\n const response = await this.tool<SetMappingResponse | null>(\"connectors:mappings:set\", {\n connectorId,\n moduleKey,\n operationKey,\n toolName,\n rowVersion,\n // Coalesced to explicit nulls rather than left `undefined`. The host treats both the same\n // way, but an explicit null states on the wire that the caller MEANT no override, which is\n // what makes a request log readable when a translation goes missing.\n requestFieldMap: requestFieldMap ?? null,\n responseFieldPaths: responseFieldPaths ?? null,\n staticRequestFields: staticRequestFields ?? null,\n });\n\n // Returned, never thrown on. The next edit's concurrency check wants this token, so losing it\n // costs a refetch — but two of the three callers this was promoted from never read the value\n // and every one of them invalidates and refetches after a save regardless. Throwing would turn\n // a host response variation into a failed save for consumers that are unaffected by it.\n //\n // FINDINGS travel with it. They used to be dropped here: the host composed them, the command\n // response carried them, and this line returned the row version alone — so CON_MAP_020 and\n // every warning beside it had never once been seen by an operator on any environment. A host\n // that sends none yields an empty list, which reads the same as a clean save.\n return {\n rowVersion: response?.rowVersion ?? null,\n findings: response?.findings ?? [],\n };\n }\n\n async deleteMapping(\n connectorId: string,\n moduleKey: string,\n operationKey: string,\n rowVersion: string,\n ): Promise<void> {\n // Row version is required by the command but not enforced on its unmap branch: clearing a\n // mapping is safe to retry blindly.\n await this.tool<unknown>(\"connectors:mappings:delete\", {\n connectorId,\n moduleKey,\n operationKey,\n rowVersion,\n });\n }\n\n async autoMatch(connectorId: string, moduleKey: string): Promise<AutoMatchResponse> {\n // The host resolves the catalogue for `moduleKey`, strips the `integrations:{slug}:` prefix\n // from the connector's tools before matching, and stores the raw verb. There is deliberately no\n // slug parameter — see `IConnectorMappingService.autoMatch`.\n const response = await this.tool<Partial<AutoMatchResponse> | null>(\n \"connectors:mappings:auto-match\",\n { connectorId, moduleKey },\n );\n\n return {\n items: response?.items ?? [],\n aiAvailable: response?.aiAvailable ?? false,\n aiUnavailableReason: response?.aiUnavailableReason ?? null,\n unmatchedRequiredCount: response?.unmatchedRequiredCount ?? 0,\n conflictReason: response?.conflictReason ?? null,\n };\n }\n\n async getMappingCompleteness(\n connectorId: string,\n moduleKey: string,\n ): Promise<MappingCompletenessResult> {\n const response = await this.tool<MappingCompletenessResult | null>(\n \"connectors:mappings:completeness\",\n { connectorId, moduleKey },\n );\n\n return response ?? { operations: [] };\n }\n\n async suggestMappingTranslation(\n connectorId: string,\n moduleKey: string,\n body: SuggestMappingTranslationBody,\n ): Promise<ConnectorMappingSuggestionResult> {\n const response = await this.tool<ConnectorMappingSuggestionResult | null>(\n \"connectors:mappings:suggest-translation\",\n {\n connectorId,\n moduleKey,\n operationKey: body.operationKey,\n toolName: body.toolName,\n // The host declares SampleArgs non-nullable; an absent sample is an empty object, not null.\n sampleArgs: body.sampleArgs ?? {},\n },\n );\n\n // An absent payload is reported as an unsuccessful suggestion rather than thrown. The editor\n // renders `success: false` as \"no suggestion available\", which is the truth; an exception would\n // read to the operator as though their mapping were broken.\n return (\n response ?? {\n success: false,\n error: \"The host returned no suggestion.\",\n // Empty objects, not null: the contract types these as `Record<string, string>`, and an\n // empty map is the accurate statement — no rewrites proposed.\n requestFieldMap: {},\n responseFieldPaths: {},\n }\n );\n }\n\n async testMapping(\n connectorId: string,\n moduleKey: string,\n body: TestMappingBody,\n ): Promise<TestToolMappingResult> {\n const response = await this.tool<TestToolMappingResult | null>(\"connectors:mappings:test\", {\n connectorId,\n moduleKey,\n operationKey: body.operationKey,\n toolName: body.toolName,\n requestFieldMap: body.requestFieldMap,\n responseFieldPaths: body.responseFieldPaths,\n staticRequestFields: body.staticRequestFields,\n sampleArgs: body.sampleArgs,\n // Omitted, not null, when absent: null would mean \"clear the column\" on the mapping PUT, and the\n // test endpoint reads absence as \"not sent\".\n ...(body.requestTemplates !== undefined && { requestTemplates: body.requestTemplates }),\n ...(body.responseItemFieldPaths !== undefined && { responseItemFieldPaths: body.responseItemFieldPaths }),\n });\n\n return (\n response ?? {\n success: false,\n error: \"The host returned no result for the mapping test.\",\n rawResponseJson: null,\n extractedFields: [],\n }\n );\n }\n\n // -------------------------------------------------------------------------\n // IConnectorEnumerationService — optional, see the interface's remarks\n // -------------------------------------------------------------------------\n\n async listConnectors(): Promise<ConnectorSummary[]> {\n const response = await this.tool<{ items?: ConnectorSummary[] } | null>(\n \"settings:list-connectors\",\n {},\n );\n\n return (response?.items ?? []).map((c) => ({\n id: c.id,\n name: c.name,\n slug: c.slug ?? null,\n isEnabled: c.isEnabled,\n }));\n }\n\n async getModuleProvider(moduleKey: string): Promise<ModuleProvider> {\n const response = await this.tool<ModuleProvider | null>(\"connectors:mappings:get-provider\", {\n moduleKey,\n });\n\n // Null-tolerant like every other read here, and the absent state is the UNCONFIGURED one\n // rather than an error: a module nobody has chosen a provider for is the normal starting\n // point, not a failure.\n return {\n targetKind: response?.targetKind ?? null,\n connectorId: response?.connectorId ?? null,\n targetExtensionId: response?.targetExtensionId ?? null,\n rowVersion: response?.rowVersion ?? null,\n };\n }\n\n async setModuleProvider(\n moduleKey: string,\n targetKind: ModuleProviderKind,\n // Nullable rather than optional, for the same reason the translation members are: the host\n // stores exactly one of these and refuses a row whose two halves disagree, so which one is\n // absent is a thing the caller states rather than omits.\n connectorId: string | null,\n targetExtensionId: string | null,\n // The token read from `getModuleProvider`, or `null` for a module that has never had a\n // selection. Handed back unchanged — see the class remarks on opaque row versions.\n rowVersion: string | null,\n ): Promise<ModuleProvider> {\n const response = await this.tool<ModuleProvider | null>(\"connectors:mappings:set-provider\", {\n moduleKey,\n targetKind,\n connectorId,\n targetExtensionId,\n rowVersion,\n });\n\n // The refreshed token comes back on the write, so a caller can change the provider twice\n // running without a re-read in between. Falling back to null rather than to the token just\n // SENT is deliberate: a stale token presented as current is how an optimistic-concurrency check\n // gets silently defeated, and a null makes the next write re-read instead.\n return {\n targetKind: response?.targetKind ?? targetKind,\n connectorId: response?.connectorId ?? null,\n targetExtensionId: response?.targetExtensionId ?? null,\n rowVersion: response?.rowVersion ?? null,\n };\n }\n\n async listConnectorTools(connectorId: string): Promise<ConnectorToolSummary[]> {\n const response = await this.tool<{ tools?: ConnectorToolSummary[] } | null>(\n \"settings:get-connector\",\n { id: connectorId },\n );\n\n // Disabled tools are returned, not filtered. Hiding one makes \"the tool I want is missing\"\n // indistinguishable from \"the tool I want is switched off\", which need different fixes. The\n // picker filters at the projection step instead — see `mapConnectorToolsToOptions`.\n return response?.tools ?? [];\n }\n\n // -------------------------------------------------------------------------\n // The PLUGIN-TARGET surface - declared, and deliberately not reachable yet\n //\n // The per-module PROVIDER choice above is reachable: CoreConnect-Api #1329 added\n // `connectors:mappings:get-provider` and `connectors:mappings:set-provider`, so an operator can\n // record that a module is served by an installed app. What they still cannot do from a plugin UI\n // is everything BELOW that choice:\n //\n // - the candidate list has no endpoint in ANY transport, so who is on offer has to be supplied to\n // this package rather than fetched by it;\n // - `connectors:mappings:list` takes a connectorId and its rows carry no TargetKind or\n // TargetExtensionId, so there is no connector-less read for a plugin-served module;\n // - `SetPluginTargetMapping` exists as a REST route only\n // (`PUT /api/v1/connectors/modules/{moduleKey}/operations/{operationKey}/plugin-target`), and a\n // plugin's UI reaches the host over MCP, so the per-operation plugin-target write cannot be\n // called from here.\n //\n // Implemented as refusals rather than omitted so the shape a real transport must satisfy is\n // written down in one place, and so the gap is a message an operator's log can carry rather than\n // a method that silently is not there. `isPluginTargetSurfaceAvailable` is the flag a caller\n // reads; none of the methods below is called while it is false.\n // -------------------------------------------------------------------------\n\n /**\n * Whether this transport can serve the plugin-target methods below. `false` here, and the three\n * methods throw, for the reasons in the block comment above.\n *\n * A property and not a guess: \"the method exists\" and \"the method works\" are different facts, and\n * a caller that probed by calling and catching would turn a known gap into a request, an error\n * toast and a support ticket.\n */\n public readonly isPluginTargetSurfaceAvailable = false;\n\n async listPluginProviders(moduleKey: string): Promise<PluginProviderCandidate[]> {\n void moduleKey;\n pluginTargetSurfaceUnavailable(\n \"listPluginProviders\",\n \"The host exposes no candidate listing in any transport. Its routable-tool listing is \" +\n \"documented as unsafe to show an end user verbatim because it applies no caller-permission \" +\n \"filter, and the write path additionally refuses any tool the consuming application did \" +\n \"not declare in its manifest - so the candidate set is a server-side decision, not a \" +\n \"client-side filter of a broader list.\",\n );\n }\n\n async getPluginTargetMappings(moduleKey: string): Promise<PluginTargetMappingItem[]> {\n void moduleKey;\n pluginTargetSurfaceUnavailable(\n \"getPluginTargetMappings\",\n \"The host's mapping list read is scoped BY connector (connectors:mappings:list takes a \" +\n \"connectorId) and its rows carry no TargetKind or TargetExtensionId, so a plugin-target \" +\n \"row has neither a connector to scope it by nor a way to be recognised in the result. No \" +\n \"connector-less list exists.\",\n );\n }\n\n async setPluginTargetMapping(\n moduleKey: string,\n args: SetPluginTargetMappingArgs,\n ): Promise<SetPluginTargetMappingResult> {\n void moduleKey;\n void args;\n pluginTargetSurfaceUnavailable(\n \"setPluginTargetMapping\",\n \"The host serves PUT /api/v1/connectors/modules/{moduleKey}/operations/{operationKey}\" +\n \"/plugin-target over REST only; there is no connectors:mappings:* tool for it, and a \" +\n \"plugin's UI reaches the host over MCP.\",\n );\n }\n}\n\n/** Every host tool this service dispatches, for `useToolInvokerMap`. Frozen and constant-length. */\nexport const CONNECTOR_MAPPING_TOOLS = [\n \"connectors:mappings:list\",\n \"connectors:mappings:set\",\n \"connectors:mappings:delete\",\n \"connectors:mappings:auto-match\",\n \"connectors:mappings:completeness\",\n \"connectors:mappings:suggest-translation\",\n \"connectors:mappings:test\",\n \"connectors:mappings:get-provider\",\n \"connectors:mappings:set-provider\",\n] as const;\n\n/**\n * The two enumeration tools, listed separately so a plugin that has its own connector service does\n * not have to declare invokers for tools it never calls. Spread both when using the default\n * enumeration implementation.\n */\nexport const CONNECTOR_ENUMERATION_TOOLS = [\n \"settings:list-connectors\",\n \"settings:get-connector\",\n] as const;\n","/**\n * React-query keys for a connector's tool mappings and the completeness read derived from them.\n *\n * @remarks\n * The completeness key is built as a CHILD of the mappings key, and that nesting is the whole point\n * of shipping these from the SDK rather than letting each plugin declare its own.\n *\n * Completeness is derived state: it is the mappings joined against the live tool schemas. Every write\n * that changes a mapping can change it. When the two keys are siblings, a mutation has to remember to\n * invalidate both, and one plugin duly invalidated only the mappings key — so its \"operations aren't\n * fully mapped\" banner went stale after every save, clear and auto-match, and stayed stale until the\n * operator navigated away. Nesting means a single `invalidateQueries({ queryKey: mappings })` takes\n * the completeness read with it, and there is nothing left to forget.\n *\n * @example\n * ```ts\n * const mappings = connectorMappingsQueryKey(connectorId, moduleKey);\n *\n * useQuery({ queryKey: mappings, queryFn: ... });\n * useQuery({ queryKey: connectorMappingCompletenessQueryKey(connectorId, moduleKey), queryFn: ... });\n *\n * // One call. Covers both, and any future query keyed beneath the mappings key.\n * queryClient.invalidateQueries({ queryKey: mappings });\n * ```\n */\n\n/** Root segment for every connector-mapping query, so one prefix can invalidate the family. */\nconst CONNECTOR_MAPPINGS_ROOT = \"connector-mappings\" as const;\n\n/**\n * Key for the mappings list of one connector + module.\n *\n * `connectorId` is accepted as possibly-undefined because the page renders before a connector is\n * chosen; pair it with `enabled: !!connectorId` rather than branching on the key.\n */\nexport function connectorMappingsQueryKey(\n connectorId: string | undefined,\n moduleKey: string,\n) {\n return [CONNECTOR_MAPPINGS_ROOT, connectorId, moduleKey] as const;\n}\n\n/**\n * The whole family's prefix, for the one invalidation that has to cross connectors.\n *\n * Almost every write is scoped to one connector and should invalidate only that connector's subtree\n * via {@link connectorMappingsQueryKey}. Changing a module's PROVIDER is the exception: it moves the\n * module from one provider's rows to another's, so the subtree that must be discarded is the one\n * being left, which the new scope's key does not name.\n */\nexport function connectorMappingsRootQueryKey() {\n return [CONNECTOR_MAPPINGS_ROOT] as const;\n}\n\n// The provider selection's own key lives with the hook that owns it, in\n// `connector-mappings/useModuleProvider` - `moduleProviderQueryKey` there. It is inside this family\n// (`[\"connector-mappings\", \"module-provider\", moduleKey]`) so the root invalidation above covers it,\n// but NOT beneath a connector id: the selection is what CHOOSES the connector, so nesting it under\n// one would move the cache entry every time the operator switched and lose it at the single moment\n// it has to be correct.\n\n/**\n * Key for the mapping rows of a module served by an installed application.\n *\n * A sibling of {@link connectorMappingsQueryKey} rather than a variant of it, because the read\n * behind it is a different one: the connector list read is scoped by connector id and a\n * plugin-target row has none. Keyed under the same root so the family invalidation covers it.\n */\nexport function pluginTargetMappingsQueryKey(moduleKey: string) {\n return [CONNECTOR_MAPPINGS_ROOT, \"plugin-target\", moduleKey] as const;\n}\n\n/**\n * Key for the completeness read, nested UNDER {@link connectorMappingsQueryKey} so invalidating the\n * mappings prefix always covers it. Do not flatten this into a sibling key.\n */\nexport function connectorMappingCompletenessQueryKey(\n connectorId: string | undefined,\n moduleKey: string,\n) {\n return [...connectorMappingsQueryKey(connectorId, moduleKey), \"completeness\"] as const;\n}\n","import type { MappingCompletenessItem } from \"./types\";\n\n/**\n * An operation is INCOMPLETE when it has at least one required canonical field that does not map\n * onto the live tool's schema. Deliberately not \"has no tool\": see {@link MappingCompletenessItem}.\n */\nfunction hasUnmappedRequiredField(op: MappingCompletenessItem): boolean {\n return op.unmappedRequiredCanonicalFields.length > 0;\n}\n\n/**\n * Every operation with a gap, required or optional. Use it to decide whether to mount a banner at\n * all, without duplicating the predicate.\n */\nexport function selectIncompleteOperations(\n operations: MappingCompletenessItem[],\n): MappingCompletenessItem[] {\n return operations.filter(hasUnmappedRequiredField);\n}\n\n/**\n * REQUIRED operations with a gap. These genuinely block the module: the connector cannot run them.\n */\n/**\n * Required operations with NO TOOL MAPPED AT ALL.\n *\n * <b>The gap none of the selectors below could report.</b> Every other selector here keys on\n * `unmappedRequiredCanonicalFields`, which describes a FIELD-translation gap. An operation that\n * declares no required canonical fields therefore has an empty array whether or not a tool answers\n * it - so a required operation with nothing mapped to it was invisible to this banner entirely.\n *\n * That is not a corner case. `find-companies` on the sales prospecting module declares no request\n * fields, so removing its tool mapping left the module's own page silent while the module selector\n * beside it marked the module as needing attention. An operator was told there was a problem by a\n * chip in a dropdown and given nowhere on the page to find out what.\n *\n * Reported SEPARATELY from the field selectors rather than folded into them, because the remedy\n * differs: these need a tool chosen, and field translation is a question that only arises once one\n * is. A consumer rendering both must therefore exclude these from the field lists - see\n * `MappingCompletenessBanner` - or an operation with both problems appears twice and the more\n * urgent half is buried under the less urgent one.\n */\nexport function selectRequiredUnmappedOperations(\n operations: readonly MappingCompletenessItem[],\n): MappingCompletenessItem[] {\n return operations.filter((op) => op.isRequired && !op.isToolMapped);\n}\n\nexport function selectRequiredIncompleteOperations(\n operations: MappingCompletenessItem[],\n): MappingCompletenessItem[] {\n return operations.filter((op) => op.isRequired && hasUnmappedRequiredField(op));\n}\n\n/**\n * OPTIONAL operations with a gap. Informational only: the operation powers an extra capability that\n * stays off until its fields are mapped, and the connector may legitimately expose no suitable tool.\n *\n * @remarks\n * Treat these as a note, never a warning. A connector need not expose every operation a module can\n * use, so a correctly configured connector can sit here indefinitely — and a banner that never\n * clears is a banner operators learn to ignore, which costs you the required warnings too.\n */\nexport function selectOptionalIncompleteOperations(\n operations: MappingCompletenessItem[],\n): MappingCompletenessItem[] {\n return operations.filter((op) => !op.isRequired && hasUnmappedRequiredField(op));\n}\n\n/** A module's readiness, counted over REQUIRED operations only. */\nexport interface MappingReadiness {\n /** Required operations that are mapped AND have no field gaps. */\n complete: number;\n /** Total required operations declared by the module's catalogue. */\n total: number;\n /** True when every required operation is mapped and complete. */\n isReady: boolean;\n}\n\n/**\n * Readiness over REQUIRED operations only, for a progress indicator or a \"ready\" badge.\n *\n * @remarks\n * Optional operations are excluded on purpose. Counting them would leave a properly configured\n * connector permanently short of 100%, for operations the operator was never expected to map. This\n * rule came from `ethisyscore-plugin-timesheets`, which had it right while another plugin was\n * reporting optional field gaps in a standing banner.\n */\nexport function selectMappingReadiness(\n operations: MappingCompletenessItem[],\n): MappingReadiness {\n const required = operations.filter((op) => op.isRequired);\n const complete = required.filter(\n (op) => op.isToolMapped && op.unmappedRequiredCanonicalFields.length === 0,\n ).length;\n\n return {\n complete,\n total: required.length,\n isReady: required.length > 0 && complete === required.length,\n };\n}\n","import {\n useQueries,\n useQuery,\n type QueryKey,\n type UseQueryOptions,\n type UseQueryResult,\n} from \"@tanstack/react-query\";\n\n/**\n * Plugin shim for the monolith's `useAuthenticatedQuery`.\n *\n * In the host realm the MCP transport is already authenticated and scoped to\n * the active organisation, so there is no auth gate to apply. The hook keeps\n * the monolith's return shape (`isLoading` + `isAuthLoading`) so lifted data\n * hooks and pages consume it unchanged — `isAuthLoading` is always `false`.\n */\nexport interface AuthenticatedQueryResult<TData, TError>\n extends Omit<UseQueryResult<TData, TError>, \"isLoading\"> {\n isLoading: boolean;\n isAuthLoading: boolean;\n}\n\nexport function useAuthenticatedQuery<\n TQueryFnData = unknown,\n TError = Error,\n TData = TQueryFnData,\n TQueryKey extends QueryKey = QueryKey,\n>(\n options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,\n): AuthenticatedQueryResult<TData, TError> {\n const result = useQuery(options);\n return {\n ...result,\n isLoading: result.isLoading,\n isAuthLoading: false,\n };\n}\n\nexport function useAuthenticatedQueries<TQueryFnData = unknown, TError = Error>(\n queries: UseQueryOptions<TQueryFnData, TError>[],\n) {\n return useQueries({ queries });\n}\n","import {\n connectorMappingCompletenessQueryKey,\n connectorMappingsQueryKey,\n} from \"../components/connectors/mappingQueryKeys\";\nimport {\n selectMappingReadiness,\n type MappingReadiness,\n} from \"../components/connectors/mappingCompleteness\";\nimport { useAuthenticatedQuery } from \"../platform-react/useAuthenticatedQuery\";\nimport type { IConnectorMappingService } from \"./IConnectorMappingService\";\nimport type { ConnectorMappingItem, MappingCompletenessResult } from \"./types\";\n\n/**\n * What every hook in this module needs to address one connector + module pair.\n *\n * `moduleKey` is a plain input, never a constant baked into the hook, and that is what makes\n * multi-module usage first-class rather than an afterthought: a consumer with twelve catalogues\n * passes whichever the operator selected and the query keys, the invalidation and the auto-match\n * trigger all re-key with it. A consumer with one catalogue passes a module-scope constant. Two of\n * the three plugins this was promoted from hardcoded their single module INSIDE the hook, which is\n * why the twelve-module one could not reuse them.\n */\nexport interface ConnectorMappingScope {\n /** Mapping transport. Injected so a consumer can supply a mock or its own. */\n service: IConnectorMappingService;\n\n /**\n * Selected connector, or `undefined` before the operator has chosen one. Reads stay disabled\n * while it is absent rather than firing against a placeholder id.\n */\n connectorId: string | undefined;\n\n /** Wire key the platform stores mappings under, e.g. `repo-monitoring`. */\n moduleKey: string;\n}\n\n/** Return shape of {@link useConnectorMappings}. */\nexport interface UseConnectorMappingsResult {\n mappings: ConnectorMappingItem[];\n isLoading: boolean;\n isFetching: boolean;\n\n /**\n * When the current data was fetched. A real timestamp, not a placeholder: the auto-match trigger\n * gates on this being newer than the last connector switch, so a constant would either fire on\n * stale data or never fire at all.\n */\n dataUpdatedAt: number;\n\n error: Error | undefined;\n refetch: () => void;\n}\n\n/**\n * The mapping rows for one connector + module. Pure read state; pair it with\n * `useConnectorMappingMutations` for the write side, which reads this hook's cache entry to keep a\n * partial write from destroying a translation.\n *\n * @remarks\n * No `placeholderData`. The auto-match trigger inspects `mappings.length` immediately after a\n * connector switch, and keeping the previous connector's rows visible would make an unmapped\n * connector look mapped and skip the trigger.\n */\nexport function useConnectorMappings({\n service,\n connectorId,\n moduleKey,\n}: ConnectorMappingScope): UseConnectorMappingsResult {\n const query = useAuthenticatedQuery<ConnectorMappingItem[]>({\n queryKey: connectorMappingsQueryKey(connectorId, moduleKey),\n queryFn: () => service.getMappings(connectorId!, moduleKey),\n enabled: !!connectorId && !!moduleKey,\n meta: { errorTitle: \"Error loading tool mappings\" },\n });\n\n return {\n mappings: query.data ?? [],\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n dataUpdatedAt: query.dataUpdatedAt,\n error: query.error ?? undefined,\n refetch: () => void query.refetch(),\n };\n}\n\n/** Return shape of {@link useConnectorMappingCompleteness}. */\nexport interface UseConnectorMappingCompletenessResult {\n operations: MappingCompletenessResult[\"operations\"];\n\n /**\n * Readiness over REQUIRED operations only, so a correctly configured connector can reach 100%.\n * Counting optional operations would leave it permanently short, for operations the operator was\n * never expected to map. See `selectMappingReadiness`.\n */\n readiness: MappingReadiness;\n\n isLoading: boolean;\n isFetching: boolean;\n}\n\n/**\n * Per-operation mapping completeness — the data behind `MappingCompletenessBanner`.\n *\n * @remarks\n * Keyed with {@link connectorMappingCompletenessQueryKey}, which is a CHILD of the mappings key.\n * That nesting is load-bearing: completeness is the mappings joined against the live tool schemas,\n * so every write can change it, and as sibling keys a mutation had to remember to invalidate two.\n * One plugin invalidated only the mappings key and its banner went stale after every save, clear\n * and auto-match until the operator navigated away.\n */\nexport function useConnectorMappingCompleteness({\n service,\n connectorId,\n moduleKey,\n}: ConnectorMappingScope): UseConnectorMappingCompletenessResult {\n const query = useAuthenticatedQuery<MappingCompletenessResult>({\n queryKey: connectorMappingCompletenessQueryKey(connectorId, moduleKey),\n queryFn: () => service.getMappingCompleteness(connectorId!, moduleKey),\n enabled: !!connectorId && !!moduleKey,\n meta: { errorTitle: \"Error loading mapping completeness\" },\n });\n\n const operations = query.data?.operations ?? [];\n\n return {\n operations,\n readiness: selectMappingReadiness(operations),\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n };\n}\n","/**\n * Host toast bridge — the ergonomic surface a lifted monolith `useToast()`\n * expects, shipped alongside the other shared composites so a plugin's\n * `components/shared/*` (and its `use-toast` alias) resolve here.\n *\n * PlatformReact pages can't import the host's toaster (it's an app-global\n * singleton, not in the host module registry), so these dispatch the\n * `ethisys:toast` CustomEvent the host's toast listener (`useExtensionToast`)\n * forwards to its toaster — the same wire contract as the lower-level\n * `@ethisyscore/components-react` `showToast` primitive (kept self-contained\n * here to avoid a load-bearing cross-package import for a one-line dispatch).\n * Outside a host that wired the listener (mock/dev), every call is a harmless\n * no-op, so a plugin keeps its lifted `showSuccessToast(...)` call sites.\n */\ntype ToastVariant = \"default\" | \"success\" | \"error\" | \"warning\";\n\nfunction dispatchToast(title: string, description: string | undefined, variant: ToastVariant): void {\n if (typeof window === \"undefined\" || title.length === 0) {\n return;\n }\n window.dispatchEvent(new CustomEvent(\"ethisys:toast\", { detail: { title, description, variant } }));\n}\n\nexport function showSuccessToast(title: string, description?: string): void {\n dispatchToast(title, description, \"success\");\n}\n\nexport function showErrorToast(title: string, description?: string): void {\n dispatchToast(title, description, \"error\");\n}\n\nexport function showWarningToast(title: string, description?: string): void {\n dispatchToast(title, description, \"warning\");\n}\n\n/**\n * Options accepted by the `toast(...)` returned from {@link useToast}. Both `title`\n * and `description` are optional, but at least one must be set — `toast({ description })`\n * shows the description as the message (mirroring the monolith); `toast({})` is a no-op.\n */\nexport interface ToastOptions {\n /** Primary line. Falls back to {@link ToastOptions.description} when absent/empty. */\n title?: string;\n /** Secondary line — or the message itself when no `title` is given. */\n description?: string;\n /** `\"destructive\"`/`\"error\"` → error; `\"warning\"`/`\"success\"` map directly; else default. */\n variant?: string;\n}\n\n/**\n * Hook mirror of the monolith's `useToast()` surface, so a lifted\n * `const { toast } = useToast()` (and `showSuccessToast`/`showErrorToast`) keeps\n * working unchanged once `@/hooks/common/utility/use-toast` is aliased here.\n */\nexport function useToast() {\n return {\n toast: (opts: ToastOptions): void => {\n const variant: ToastVariant =\n opts.variant === \"destructive\" || opts.variant === \"error\"\n ? \"error\"\n : opts.variant === \"warning\"\n ? \"warning\"\n : opts.variant === \"success\"\n ? \"success\"\n : \"default\";\n // `title` is the primary line; fall back to `description` so `toast({ description })`\n // shows it (mirroring the monolith) instead of silently no-op'ing. Both absent → no-op.\n if (opts.title) {\n dispatchToast(opts.title, opts.description, variant);\n } else if (opts.description) {\n dispatchToast(opts.description, undefined, variant);\n }\n },\n showSuccessToast,\n showErrorToast,\n showWarningToast,\n };\n}\n","/**\n * Wire shapes for the connector tool-mapping surface — the DTOs the host's `connectors:mappings:*`\n * tools return, plus the argument shapes the hooks in this module accept.\n *\n * Declared here rather than in a plugin's generated contracts for the same reason the completeness\n * shapes moved to `components/connectors/types`: this package sits BELOW the plugins, and three of\n * them held their own copy of every type below. The copies are what let the surfaces drift.\n *\n * The rendering-side shapes (`MappingCompletenessItem`, `ConnectorCanonicalField`,\n * `TestToolMappingRequest`, …) are NOT redeclared here. They already live in\n * `components/connectors/types` next to the components that consume them, and are re-exported by\n * this module's barrel so a consumer needs one import.\n */\nimport type {\n ConnectorCanonicalField,\n ConnectorMappingSuggestionResult,\n MappingCompletenessResult,\n TestToolMappingResult,\n} from \"../components/connectors/types\";\n\n// ---------------------------------------------------------------------------\n// Wire vocabularies\n//\n// `as const` objects rather than bare unions so a render path can compare against a named member.\n// The union types are derived from them, so the two cannot drift.\n// ---------------------------------------------------------------------------\n\n/** How a mapping came to exist. Mirrors the backend `ToolMappingSource` enum (snake_case wire). */\nexport const MAPPING_SOURCE = {\n keyword: \"keyword\",\n ai: \"ai\",\n manual: \"manual\",\n} as const;\n\nexport type ConnectorMappingSource = (typeof MAPPING_SOURCE)[keyof typeof MAPPING_SOURCE];\n\n/** Why the AI matcher was unavailable for an auto-match run. `null` means it was available. */\nexport const AI_UNAVAILABLE_REASON = {\n Timeout: \"timeout\",\n ProviderError: \"provider_error\",\n ParseError: \"parse_error\",\n ContextTooLarge: \"context_too_large\",\n NoConnectorConfigured: \"no_connector_configured\",\n} as const;\n\nexport type ConnectorAiUnavailableReason =\n | (typeof AI_UNAVAILABLE_REASON)[keyof typeof AI_UNAVAILABLE_REASON]\n | null;\n\n/** Why a concurrent auto-match request was refused. `null` means no conflict. */\nexport const AUTO_MATCH_CONFLICT_REASON = {\n AnotherAutoMatchInProgress: \"another_auto_match_in_progress\",\n} as const;\n\nexport type ConnectorAutoMatchConflictReason =\n | (typeof AUTO_MATCH_CONFLICT_REASON)[keyof typeof AUTO_MATCH_CONFLICT_REASON]\n | null;\n\n/**\n * What a mapping resolves to, as this surface spells it: a connector to a third-party system, or\n * another installed application.\n *\n * Lower-case, and NOT the string the host stores. The host persists its `ToolMappingTargetKind` by\n * NAME and case (`\"Connector\"` / `\"Plugin\"`), so the two vocabularies are declared separately and\n * converted by `toWireTargetKind` / `fromWireTargetKind` in `targetSelection`. Sharing one literal\n * would make a render-side comparison and a wire value the same string by coincidence, and the\n * first time either side changed case the other would fail silently - the stored value matching\n * neither branch of the host's resolver nor its CHECK constraint.\n */\nexport const MAPPING_TARGET_KIND = {\n connector: \"connector\",\n plugin: \"plugin\",\n} as const;\n\nexport type MappingTargetKind = (typeof MAPPING_TARGET_KIND)[keyof typeof MAPPING_TARGET_KIND];\n\n/**\n * Which kind of thing serves a module. The wire values are the host's\n * `ToolMappingTargetKind` member NAMES, so they are a contract rather than a display label.\n */\nexport const MODULE_PROVIDER_KIND = {\n Connector: \"Connector\",\n Plugin: \"Plugin\",\n} as const;\n\nexport type ModuleProviderKind = (typeof MODULE_PROVIDER_KIND)[keyof typeof MODULE_PROVIDER_KIND];\n\n/**\n * The same vocabulary as the host stores and matches it, keyed the way the UI spells its kinds so a\n * render-side value converts without a cast. See {@link MAPPING_TARGET_KIND}.\n *\n * The STRINGS come from {@link MODULE_PROVIDER_KIND} rather than being retyped, because this file\n * arrived at the same contract twice - once for the per-operation target and once for the\n * per-module provider - and two literal copies of a value the host matches by name is the drift\n * this vocabulary split exists to prevent, not an instance of it.\n */\nexport const MAPPING_TARGET_KIND_WIRE = {\n connector: MODULE_PROVIDER_KIND.Connector,\n plugin: MODULE_PROVIDER_KIND.Plugin,\n} as const;\n\nexport type MappingTargetKindWire =\n (typeof MAPPING_TARGET_KIND_WIRE)[keyof typeof MAPPING_TARGET_KIND_WIRE];\n\n// ---------------------------------------------------------------------------\n// Mapping rows\n// ---------------------------------------------------------------------------\n\n/**\n * The operator-editable translation attached to one mapping row.\n *\n * @remarks\n * All three members are `null`-able and **all three must be sent on every write**. The host's\n * `connectors:mappings:set` treats an omitted or null member as \"no operator override\" and writes\n * NULL, so a partial write silently destroys whichever members it left out. This is not a style\n * point: picking a tool used to clear an operator's field translations, confirmed against the\n * production database. {@link ConnectorMappingWriteArgs} and `useConnectorMappingMutations` are what\n * make a write complete; see the fill-in in `useConnectorMappingMutations`.\n */\nexport interface ConnectorMappingTranslationState {\n /** Canonical request field → tool input field name. `null` ⇒ identity translation. */\n requestFieldMap: Record<string, string> | null;\n\n /** Canonical response field → JSON path against the tool's response. `null` ⇒ identity. */\n responseFieldPaths: Record<string, string> | null;\n\n /**\n * Constant tool-request parameters injected into every call regardless of the canonical input\n * (tool field name → literal value, e.g. `method: \"get_reviews\"`). `null` ⇒ none configured.\n */\n staticRequestFields: Record<string, string> | null;\n}\n\n/** One row of the operation → tool mapping table, as `connectors:mappings:list` returns it. */\nexport interface ConnectorMappingItem {\n /** Canonical operation key, e.g. `list-commits`. */\n operationKey: string;\n\n /** Human label, from the module's catalogue. */\n label: string;\n\n /** Whether the module declares this operation as required. */\n isRequired: boolean;\n\n /** Tool the operation currently resolves to, or `null` when unmapped. */\n mappedToolName: string | null;\n\n source: ConnectorMappingSource | null;\n\n /** AI match confidence in `0..1`, or `null` for keyword/manual matches. */\n confidence: number | null;\n\n /**\n * Opaque concurrency token. The host declares it as `byte[]`, which crosses the wire as base64:\n * read one here and hand the same string back on write. `null` means no stored row exists yet,\n * which callers send as `\"\"` so the host upserts.\n */\n rowVersion: string | null;\n\n /** True when the row is an AI match below the auto-accept threshold and wants an operator's eye. */\n requiresReview?: boolean;\n\n /**\n * Canonical request schema declared by the operation's owning module. `null` when the operation\n * declares none, in which case there is nothing operator-meaningful to translate and the\n * translation affordance stays hidden.\n */\n canonicalRequestFields?: ConnectorCanonicalField[] | null;\n\n /** Canonical response schema. Same null semantics as {@link canonicalRequestFields}. */\n canonicalResponseFields?: ConnectorCanonicalField[] | null;\n\n requestFieldMap?: Record<string, string> | null;\n responseFieldPaths?: Record<string, string> | null;\n staticRequestFields?: Record<string, string> | null;\n}\n\n// ---------------------------------------------------------------------------\n// Auto-match\n// ---------------------------------------------------------------------------\n\n/** One operation's outcome from an auto-match run. Mirrors `ConnectorAutoMatchItem`. */\nexport interface ConnectorAutoMatchItem {\n operationKey: string;\n\n /**\n * `null` when this operation matched nothing. An item is returned for EVERY operation regardless,\n * so `items.length` is the operation count and not the match count — reading it as the latter\n * reports success on a run that mapped nothing.\n */\n mappedToolName: string | null;\n\n source: ConnectorMappingSource | null;\n confidence: number | null;\n requiresReview?: boolean;\n\n /**\n * A tool that keyword-matched but was rejected, and the required parameters it could not be\n * satisfied with. The host has always returned both; the types this was promoted from omitted\n * them in two of three plugins, which discarded the only machine-readable explanation of why an\n * auto-match came back empty.\n */\n discardedToolName?: string | null;\n unsatisfiedToolParameters?: string[] | null;\n}\n\n/** Raw auto-match result, before {@link AutoMatchSummary} reduces it for the banner. */\nexport interface AutoMatchResponse {\n items: ConnectorAutoMatchItem[];\n aiAvailable: boolean;\n aiUnavailableReason: ConnectorAiUnavailableReason;\n unmatchedRequiredCount: number;\n conflictReason: ConnectorAutoMatchConflictReason;\n}\n\n/** Per-source counts reduced from an {@link AutoMatchResponse}, for the outcome banner. */\nexport interface AutoMatchSummary {\n aiAvailable: boolean;\n aiUnavailableReason: ConnectorAiUnavailableReason;\n unmatchedRequiredCount: number;\n matchedByKeyword: number;\n matchedByAi: number;\n conflictReason: ConnectorAutoMatchConflictReason;\n}\n\n// ---------------------------------------------------------------------------\n// Write / request bodies\n// ---------------------------------------------------------------------------\n\n/**\n * Arguments for one mapping write.\n *\n * The three translation members are OPTIONAL here and required on the wire, and the gap between\n * those two facts is deliberate: a caller that is only changing the tool omits them, and\n * `useConnectorMappingMutations` fills each one from the row it already holds before the write\n * leaves. Omitting a member therefore means \"leave it as it is\", never \"clear it\". A caller that\n * genuinely wants to clear one passes an explicit `null`.\n */\nexport interface ConnectorMappingWriteArgs {\n operationKey: string;\n\n /** `\"\"` is the unmap request; the hooks route it to `connectors:mappings:delete`. */\n toolName: string;\n\n /** Row version read from {@link ConnectorMappingItem.rowVersion}, or `\"\"` for a fresh row. */\n rowVersion: string;\n\n requestFieldMap?: Record<string, string> | null;\n responseFieldPaths?: Record<string, string> | null;\n staticRequestFields?: Record<string, string> | null;\n}\n\n/** Body for the per-operation AI translation suggestion. */\nexport interface SuggestMappingTranslationBody {\n operationKey: string;\n toolName: string;\n\n /** Sample values the operator typed. The host declares this non-nullable; absent ⇒ `{}`. */\n sampleArgs?: Record<string, unknown>;\n}\n\n/** Body for the translation dry-run. `connectorId` / `moduleKey` are passed alongside, not here. */\nexport interface TestMappingBody {\n operationKey: string;\n toolName: string;\n requestFieldMap: Record<string, string> | null;\n responseFieldPaths: Record<string, string> | null;\n\n /** The DRAFT static params, so the dry-run exercises the discriminator the operator is testing. */\n staticRequestFields: Record<string, string> | null;\n sampleArgs: Record<string, unknown>;\n\n /**\n * The DRAFT request templates and row fields, same names and shapes as the mapping PUT. Optional:\n * absent means not sent, so the dry-run uses the stored ones.\n */\n requestTemplates?: Record<string, unknown>[] | null;\n responseItemFieldPaths?: Record<string, Record<string, string>> | null;\n}\n\n// ---------------------------------------------------------------------------\n// Connector enumeration (the picker's data, not the mapping surface's)\n// ---------------------------------------------------------------------------\n\n/** A connector as `settings:list-connectors` projects it. */\nexport interface ConnectorSummary {\n id: string;\n name: string;\n slug?: string | null;\n isEnabled: boolean;\n}\n\n/** One tool a connector exposes, as `settings:get-connector` projects it. */\nexport interface ConnectorToolSummary {\n toolName: string;\n description?: string | null;\n isEnabled: boolean;\n}\n// ---------------------------------------------------------------------------\n// Per-module provider selection\n//\n// The level ABOVE the per-operation mapping table, and keeping the two distinct is the point. This\n// records which provider serves a module; the table below it records which of that provider's tools\n// answers each operation. The host stores them as separate rows for a reason worth repeating: its\n// auto-match writes a candidate mapping row per connector, so the mapping table on its own cannot\n// answer \"where does this module get its data from\", and any rule for picking between several rows\n// without a recorded selection - highest confidence, most recent, lowest id - is a guess dressed up\n// as a policy.\n// ---------------------------------------------------------------------------\n\n/**\n * Which provider an organisation has chosen to serve a module.\n *\n * @remarks\n * `targetKind` is `null` when the organisation has made NO choice, and the host deliberately does\n * not default it to `\"Connector\"`. \"Nobody has chosen yet\" and \"chose a connector that has no\n * mapping yet\" call for different remedies, and a surface that cannot tell them apart shows a\n * choice nobody made.\n */\nexport interface ModuleProvider {\n targetKind: ModuleProviderKind | null;\n connectorId: string | null;\n targetExtensionId: string | null;\n\n /**\n * Concurrency token of the stored selection, `null` when there is none yet.\n *\n * Opaque, exactly like a mapping's: read it here and hand the same string back to\n * `setModuleProvider`. Nothing on this side parses or constructs one.\n */\n rowVersion: string | null;\n}\n\n/** One tool an installed application exposes, as the provider picker renders it. */\nexport interface PluginProviderTool {\n /** Fully namespaced, e.g. `hr:get-employees-by-ids`. A plugin tool routes under this name verbatim. */\n toolName: string;\n description?: string | null;\n}\n\n/**\n * One installed application an operator may choose to serve a module, with the tools it can answer\n * the module's operations with.\n *\n * @remarks\n * **The candidate set is narrower than \"every tool that routes\", and has to be.** The host's write\n * path refuses a plugin-target mapping naming a tool the CONSUMING application did not declare in\n * its own manifest `mcpToolDependencies`, because such a row is authority to invoke another\n * application's tool with no per-tool permission check. A picker offering a wider list offers\n * choices the save will reject, so whatever supplies this must apply the same bound - and must also\n * filter by the caller's own permissions, which the host's routable-tool listing explicitly does\n * not.\n *\n * There is no endpoint that returns this, in any transport, which is why it is an INPUT to\n * `useConnectorToolMappings` rather than something this package fetches.\n */\nexport interface PluginProviderCandidate {\n extensionId: string;\n\n /** Display name of the installed application. */\n name: string;\n\n tools: PluginProviderTool[];\n}\n\n/**\n * A mapping row whose target is another installed application, as the plugin-mode table renders it.\n *\n * Same shape as {@link ConnectorMappingItem} plus the providing extension, which a connector-scoped\n * row has no need of because the connector is the scope of the read.\n */\nexport interface PluginTargetMappingItem extends ConnectorMappingItem {\n /** The providing extension, or `null` when the operation is unmapped. */\n targetExtensionId: string | null;\n}\n\n/** Arguments for one plugin-target mapping write. */\nexport interface SetPluginTargetMappingArgs {\n operationKey: string;\n\n /** The PROVIDING extension. Required by the host even on an unmap, which is how it finds the row. */\n targetExtensionId: string;\n\n /** `\"\"` is the unmap request, matching {@link ConnectorMappingWriteArgs.toolName}. */\n toolName: string;\n\n /** Row version, or `\"\"` for a fresh row. Not enforced by the host on the unmap branch. */\n rowVersion: string;\n}\n\n/** The stored plugin-target mapping after a write. */\nexport interface SetPluginTargetMappingResult {\n /** `null` when the operation is now unmapped. */\n toolName: string | null;\n targetExtensionId: string | null;\n\n /** `\"\"` after an unmap, where there is no row left to hold a token. */\n rowVersion: string;\n}\n\n// Re-exported so a consumer of this module's types does not need a second import for the shapes\n// the service returns. Type-only, so nothing is added to the built bundle.\nexport type {\n ConnectorCanonicalField,\n ConnectorMappingSuggestionResult,\n MappingCompletenessResult,\n TestToolMappingResult,\n};\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useMutation, useQueryClient } from \"@tanstack/react-query\";\n\nimport { connectorMappingsQueryKey } from \"../components/connectors/mappingQueryKeys\";\nimport {\n showSuccessToast as defaultShowSuccessToast,\n showWarningToast as defaultShowWarningToast,\n} from \"../components/shared/toast\";\nimport type { ConnectorMappingScope } from \"./useConnectorMappings\";\nimport {\n MAPPING_SOURCE,\n type AutoMatchResponse,\n type AutoMatchSummary,\n type ConnectorMappingItem,\n type ConnectorMappingWriteArgs,\n} from \"./types\";\n\n/** Identifies the row a delete targets, with the version it was read at. */\ninterface DeleteMappingArgs {\n operationKey: string;\n rowVersion: string;\n}\n\n/**\n * Per-call outcome callbacks for one mapping write.\n *\n * Additive and optional, for a caller that SEQUENCES this write after another and has to know which\n * of the two failed — \"the scope saved but the pin did not\" is a different situation for the\n * operator than either write failing alone. The shared error toast still fires either way; these\n * only let the caller record which half it was.\n */\nexport interface MappingWriteCallbacks {\n onSuccess?: () => void;\n onError?: () => void;\n}\n\n/** Options for {@link useConnectorMappingMutations}. */\nexport interface UseConnectorMappingMutationsOptions extends ConnectorMappingScope {\n /**\n * Called after any mutation that SAVES a mapping, with the connector and module it was saved\n * against.\n *\n * @remarks\n * Exists so a consumer can mirror the operator's connector choice into its own state. One plugin\n * must: its transcription and feed-poll tasks are background workers with no caller identity, so\n * they cannot enumerate connectors to discover which one holds the mapping, and without this\n * write-through they report themselves unconfigured no matter what the Tool Mappings page shows.\n * Taken as a callback so this hook keeps no domain knowledge of what a consumer stores.\n *\n * **It hangs off the mutations rather than the page's click handlers deliberately.** There are\n * three distinct ways to save a mapping — picking a tool, auto-matching, and saving field\n * translations — and the third was originally missed, which left an operator who had just fixed a\n * field mapping with a lane that still reported itself unconfigured and no way to tell why. Every\n * save funnels through here, so a fourth route cannot reintroduce that.\n *\n * Deliberately NOT called by a delete: clearing a mapping is not choosing a connector. Leaving\n * the recorded id in place makes the lane report \"a connector is chosen but has no tool mapped\",\n * which is both accurate and the actionable half.\n */\n onMappingSaved?: (connectorId: string, moduleKey: string) => void;\n\n /**\n * Success-toast sink. Defaults to the SDK's `showSuccessToast`, which dispatches the host's\n * `ethisys:toast` event and no-ops outside a host that wired the listener. Override only to\n * silence it or to route it somewhere else — the three plugins this was promoted from all\n * ultimately reached the same host event.\n */\n showSuccessToast?: (title: string, description?: string) => void;\n\n /**\n * Warning-toast sink, for the save-time gate's findings. Same defaulting and same reason as\n * {@link showSuccessToast}; separate because a finding reported through the SUCCESS sink reads as\n * part of \"saved successfully\", which is a warning nobody acts on.\n */\n showWarningToast?: (title: string, description?: string) => void;\n}\n\n/** Return shape of {@link useConnectorMappingMutations}. */\nexport interface UseConnectorMappingMutationsResult {\n /** Runs auto-match for the whole module. No-ops without a connector. */\n triggerAutoMatch: () => void;\n isAutoMatching: boolean;\n\n /**\n * Saves one mapping, or clears it when `toolName` is `\"\"`. See the fill-in in the implementation\n * for why omitting a translation member means \"leave it alone\" and not \"clear it\".\n */\n updateMapping: (args: ConnectorMappingWriteArgs, callbacks?: MappingWriteCallbacks) => void;\n isUpdating: boolean;\n\n /** Outcome of the most recent auto-match run, or `null` before one has completed. */\n autoMatchSummary: AutoMatchSummary | null;\n}\n\n/**\n * Resolves one translation member for a write: what the caller said, or the stored value when the\n * caller did not mention it.\n *\n * @remarks\n * **The `undefined` / `null` distinction is the whole function**, and getting it wrong is a defect\n * in each direction:\n *\n * - `undefined` — the caller did not mention this member. The tool picker saves three identity\n * arguments and nothing else, so filling from the stored row is what stops choosing a tool from\n * destroying the operator's field mapping. That was the production defect.\n * - `null` — the caller SAID clear it. The translation editor delivers `null` for a side whose\n * every input the operator emptied.\n *\n * The three copies this was promoted from wrote `args.requestFieldMap ?? current?.requestFieldMap\n * ?? null`, and `??` treats `null` and `undefined` alike — so an explicit clear was overwritten\n * from the cache and the editor could not clear a side at all. The operator emptied every input,\n * pressed Save, got a success toast, and the old mapping came straight back on the next refetch.\n * That is the mirror image of the defect the fill-in was added for, and it was in all three repos.\n */\n/**\n * {@link ConnectorMappingWriteArgs} after {@link resolveTranslationMember} has run: the three\n * translation members are no longer optional, because each has been decided.\n *\n * Naming it makes the boundary a TYPE rather than a comment. The service now REQUIRES all three —\n * an omitted argument there is indistinguishable from a deliberate clear — and this is what proves\n * the only route to it has resolved them. Without it the internal mutation would happily forward an\n * unresolved `undefined`, which is the same data loss one layer up.\n */\ntype ResolvedMappingWriteArgs = Omit<\n ConnectorMappingWriteArgs,\n \"requestFieldMap\" | \"responseFieldPaths\" | \"staticRequestFields\"\n> & {\n requestFieldMap: Record<string, string> | null;\n responseFieldPaths: Record<string, string> | null;\n staticRequestFields: Record<string, string> | null;\n};\n\nfunction resolveTranslationMember(\n supplied: Record<string, string> | null | undefined,\n stored: Record<string, string> | null | undefined,\n): Record<string, string> | null {\n if (supplied !== undefined) return supplied;\n return stored ?? null;\n}\n\n/**\n * Reduces an auto-match response for the outcome banner.\n *\n * Null-tolerant on purpose: the command returned no payload at all until the host gained a response\n * type, and a `Result` with no value is still representable on the wire. Throwing here would reject\n * inside `onSuccess`, where nothing handles it — a banner that reports nothing beats a crashed page.\n */\nfunction summarise(response: AutoMatchResponse | null | undefined): AutoMatchSummary {\n const items = response?.items ?? [];\n return {\n aiAvailable: response?.aiAvailable ?? false,\n aiUnavailableReason: response?.aiUnavailableReason ?? null,\n unmatchedRequiredCount: response?.unmatchedRequiredCount ?? 0,\n matchedByKeyword: items.filter((i) => i.source === MAPPING_SOURCE.keyword).length,\n matchedByAi: items.filter((i) => i.source === MAPPING_SOURCE.ai).length,\n conflictReason: response?.conflictReason ?? null,\n };\n}\n\n/**\n * The write side of the connector tool-mapping surface: auto-match, per-operation save, and clear.\n *\n * **This hook is the reason this module exists.** It was duplicated byte-for-byte across three\n * plugins and both production defects on this surface lived inside it:\n *\n * 1. **A stale completeness banner.** Two plugins declared their own query keys, one as siblings,\n * and its mutations invalidated only the mappings key. Fixed by nesting the completeness key\n * beneath the mappings key so one prefix invalidation covers both — see\n * {@link connectorMappingsQueryKey} — and there is nothing left for a mutation to forget.\n *\n * 2. **Silently destroyed field translations.** The tool-level save omitted the three translation\n * members, and because `connectors:mappings:set` treats an omitted member as \"no operator\n * override\" and writes NULL, picking a tool wiped the operator's field mapping and any pinned\n * static parameter with it. Confirmed against the production database. Fixed by the fill-in\n * below. It had to be fixed three times, in three repos, because this function was identical in\n * each — and one of the three never received the fix at all.\n *\n * 3. **The translation editor could not CLEAR a side**, which is (2)'s fix taken one step too far.\n * See {@link resolveTranslationMember}: it was present in all three repos and was found by this\n * module's tests rather than in production.\n */\nexport function useConnectorMappingMutations({\n service,\n connectorId,\n moduleKey,\n onMappingSaved,\n showSuccessToast = defaultShowSuccessToast,\n showWarningToast = defaultShowWarningToast,\n}: UseConnectorMappingMutationsOptions): UseConnectorMappingMutationsResult {\n const queryClient = useQueryClient();\n const [autoMatchSummary, setAutoMatchSummary] = useState<AutoMatchSummary | null>(null);\n\n // The summary belongs to the scope it was produced for. Without this, switching connector or\n // module leaves the previous scope's match counts and conflict state on screen — and if the new\n // scope already has mappings no auto-match runs to replace it, so the stale numbers simply stay.\n // Counts an operator reads as describing what they are looking at, describing something else, is\n // worse than showing nothing.\n const scopeKey = `${connectorId ?? \"\"}|${moduleKey}`;\n const summaryScopeRef = useRef<string>(scopeKey);\n useEffect(() => {\n if (summaryScopeRef.current === scopeKey) return;\n summaryScopeRef.current = scopeKey;\n setAutoMatchSummary(null);\n }, [scopeKey]);\n // Memoised because the callbacks below depend on it. The builder returns a fresh array every\n // call, so an unmemoised key would rebuild every callback on every render.\n const queryKey = useMemo(\n () => connectorMappingsQueryKey(connectorId, moduleKey),\n [connectorId, moduleKey],\n );\n\n // One invalidation covers the mappings read AND every query keyed beneath it, which is what\n // completeness is. Completeness is DERIVED from the mappings plus the live tool schemas, so any\n // write that changes a mapping can change the banner — including a clear, which can only ever\n // add a gap and is therefore the case where a stale banner is most misleading.\n const invalidateMappingsAndCompleteness = useCallback(() => {\n void queryClient.invalidateQueries({ queryKey });\n }, [queryClient, queryKey]);\n\n const autoMatch = useMutation({\n mutationFn: () => service.autoMatch(connectorId!, moduleKey),\n meta: { errorTitle: \"Auto-match failed\" },\n onSuccess: (response) => {\n setAutoMatchSummary(summarise(response));\n invalidateMappingsAndCompleteness();\n const matched = (response?.items ?? []).filter((i) => i.mappedToolName).length;\n // Only when something actually matched. A run that matched nothing chose no connector, so\n // there is nothing for a consumer to record.\n if (connectorId && matched > 0) {\n onMappingSaved?.(connectorId, moduleKey);\n }\n showSuccessToast(\"Auto-match complete\", `Matched ${matched} operations.`);\n },\n });\n\n const updateMapping = useMutation({\n mutationFn: (args: ResolvedMappingWriteArgs) =>\n service.updateMapping(\n connectorId!,\n moduleKey,\n args.operationKey,\n args.toolName,\n args.rowVersion,\n args.requestFieldMap,\n args.responseFieldPaths,\n args.staticRequestFields,\n ),\n meta: { errorTitle: \"Update failed\" },\n onSuccess: (outcome) => {\n invalidateMappingsAndCompleteness();\n if (connectorId) {\n onMappingSaved?.(connectorId, moduleKey);\n }\n\n // The gate's findings ride on a SUCCESS response, and until now nothing read them: this\n // callback took no argument, the service returned the row version alone, and the mapping\n // editor never rendered one. So a save that the platform had something true to say about\n // looked identical to a clean one — CON_MAP_020 among them, which had therefore never been\n // seen by an operator on any environment since it shipped.\n //\n // Reported as a WARNING toast rather than folded into the success line: a warning that reads\n // as part of \"saved successfully\" is a warning nobody acts on, and these are the findings the\n // gate deliberately does not refuse over.\n const findings = outcome?.findings ?? [];\n if (findings.length === 0) {\n showSuccessToast(\"Mapping updated\", \"The tool mapping was saved.\");\n return;\n }\n\n // The codes are in the description because they are what an operator searches for when they\n // come to ask about one.\n showWarningToast(\n findings.length === 1 ? \"Mapping saved, with a warning\" : `Mapping saved, with ${findings.length} warnings`,\n findings.map((f) => `${f.message}${f.detail ? ` ${f.detail}` : \"\"} (${f.code})`).join(\" \"),\n );\n },\n });\n\n const deleteMapping = useMutation({\n mutationFn: (args: DeleteMappingArgs) =>\n service.deleteMapping(connectorId!, moduleKey, args.operationKey, args.rowVersion),\n meta: { errorTitle: \"Remove failed\" },\n onSuccess: () => {\n invalidateMappingsAndCompleteness();\n showSuccessToast(\"Mapping removed\", \"The tool mapping was cleared.\");\n },\n });\n\n // No slug gate. The slug used to be threaded from a connector-detail fetch that could still be in\n // flight when a page auto-triggers on a connector switch, so the trigger silently skipped — one\n // of the reasons the Auto-match button appeared to do nothing. The host derives the namespace\n // from the connector id, so there is nothing to wait for.\n const triggerAutoMatch = useCallback(() => {\n if (!connectorId) return;\n autoMatch.mutate();\n }, [autoMatch, connectorId]);\n\n const updateOrDelete = useCallback(\n (args: ConnectorMappingWriteArgs, callbacks?: MappingWriteCallbacks) => {\n if (!connectorId) return;\n\n // A view emits `toolName === \"\"` when the operator clears the picker. The host's set\n // validator rejects an empty tool name, so a clear is routed to the delete tool rather than\n // surfacing as a validation error. Callers keep one `updateMapping` signature and never need\n // to know two host tools exist. An empty rowVersion means no row exists yet — nothing to\n // delete, so the clear is a no-op rather than a request that would 404.\n if (args.toolName === \"\") {\n if (args.rowVersion === \"\") return;\n deleteMapping.mutate(\n { operationKey: args.operationKey, rowVersion: args.rowVersion },\n callbacks,\n );\n return;\n }\n\n // Fill in any translation member the caller did not supply from the row we already hold.\n //\n // LOAD-BEARING, not tidiness. The platform store treats a null member as \"no operator\n // override\" and writes NULL, where a plugin's own table used to leave the column alone. So a\n // caller that changes ONLY the mapped tool — which is exactly what the tool picker does, and\n // what the one sequenced caller in `tech` does when it clears a pinned static parameter —\n // wipes the operator's field mapping and any other pinned parameter with it. Passing the\n // row's current translation back makes the write complete rather than partial.\n //\n // Auto-match is NOT one of these routes: it rewrites rows host-side and never reaches this\n // function. Its effect on a translation is the host's business, not this hook's.\n //\n // Only `undefined` — the caller not mentioning a member — is filled in. An explicit `null`\n // is a caller SAYING clear this and survives untouched; see `resolveTranslationMember`, and\n // do not collapse it back to `??`.\n //\n // Read from the query cache rather than taking the rows as an argument: this hook is used\n // alongside `useConnectorMappings`, which already populates that cache under the same key, so\n // threading the list through every caller would create a second source of the same truth.\n const rows = queryClient.getQueryData<ConnectorMappingItem[]>(queryKey) ?? [];\n const current = rows.find((m) => m.operationKey === args.operationKey);\n\n updateMapping.mutate(\n {\n ...args,\n requestFieldMap: resolveTranslationMember(args.requestFieldMap, current?.requestFieldMap),\n responseFieldPaths: resolveTranslationMember(\n args.responseFieldPaths,\n current?.responseFieldPaths,\n ),\n staticRequestFields: resolveTranslationMember(\n args.staticRequestFields,\n current?.staticRequestFields,\n ),\n },\n callbacks,\n );\n },\n [connectorId, updateMapping, deleteMapping, queryClient, queryKey],\n );\n\n return {\n triggerAutoMatch,\n isAutoMatching: autoMatch.isPending,\n updateMapping: updateOrDelete,\n isUpdating: updateMapping.isPending || deleteMapping.isPending,\n autoMatchSummary,\n };\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\n\nimport type { IConnectorMappingService } from \"./IConnectorMappingService\";\nimport type {\n ConnectorMappingSuggestionResult,\n SuggestMappingTranslationBody,\n TestMappingBody,\n TestToolMappingResult,\n} from \"./types\";\n\n/** Variables for one suggest-translation call. */\nexport interface SuggestMappingTranslationVars {\n connectorId: string;\n moduleKey: string;\n body: SuggestMappingTranslationBody;\n}\n\n/** Variables for one dry-run call. */\nexport interface TestMappingVars {\n connectorId: string;\n moduleKey: string;\n body: TestMappingBody;\n}\n\n/**\n * The opt-in \"Suggest with AI\" action.\n *\n * Read-only inference: it invalidates NOTHING, because nothing was written. The caller decides what\n * to do with the proposal — every consumer so far opens the translation editor seeded with it so\n * the operator reviews before saving.\n *\n * @remarks\n * A soft failure — AI not configured, or no confident suggestion — comes back as\n * `{ success: false, error }` rather than an HTTP error. This hook THROWS on it, so the consumer's\n * shared `meta.errorTitle` handler surfaces the reason. Resolving successfully instead is what made\n * \"Suggest with AI\" silently do nothing in the copy this was promoted from: the mutation settled\n * happily, `result.success` was false, and the caller's `onSuccess` returned early without telling\n * anyone why.\n *\n * The service itself never throws for this case (see `ConnectorMappingService`), so a consumer that\n * wants to render the soft failure inline rather than as a toast can call the service directly.\n */\nexport function useSuggestMappingTranslation(\n service: IConnectorMappingService,\n): UseMutationResult<ConnectorMappingSuggestionResult, Error, SuggestMappingTranslationVars> {\n return useMutation<ConnectorMappingSuggestionResult, Error, SuggestMappingTranslationVars>({\n mutationFn: async (vars) => {\n const result = await service.suggestMappingTranslation(\n vars.connectorId,\n vars.moduleKey,\n vars.body,\n );\n if (!result.success) {\n throw new Error(\n result.error ?? \"The AI couldn't suggest a translation for this operation.\",\n );\n }\n return result;\n },\n meta: { errorTitle: \"Suggest translation failed\" },\n });\n}\n\n/**\n * The \"Test mapping\" dry-run behind the translation editor's Test panel.\n *\n * Never throws for a tool-invocation or extraction failure: those come back as\n * `{ success: false, error }` and the editor renders them in its own panel, next to the raw\n * response the operator needs in order to fix the JSON path. Converting that into a toast would\n * discard the raw response, which is the only actionable half.\n *\n * Invalidates nothing — a dry run writes nothing.\n */\nexport function useTestMapping(\n service: IConnectorMappingService,\n): UseMutationResult<TestToolMappingResult, Error, TestMappingVars> {\n return useMutation<TestToolMappingResult, Error, TestMappingVars>({\n mutationFn: (vars) => service.testMapping(vars.connectorId, vars.moduleKey, vars.body),\n meta: { errorTitle: \"Mapping test failed\" },\n });\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport type {\n ConnectorMappingTranslationEditorProps,\n ConnectorMappingTranslationExtras,\n} from \"../components/connectors/ConnectorMappingTranslationEditorView\";\nimport type { MappingCompletenessItem } from \"../components/connectors/types\";\nimport { useSuggestMappingTranslation, useTestMapping } from \"./useMappingAdvisors\";\nimport type { MappingWriteCallbacks } from \"./useConnectorMappingMutations\";\nimport type { IConnectorMappingService } from \"./IConnectorMappingService\";\nimport type {\n ConnectorMappingItem,\n ConnectorMappingTranslationState,\n ConnectorMappingWriteArgs,\n TestToolMappingResult,\n} from \"./types\";\n\n/** Which operation the editor is open for, and the translation it was seeded with. */\ninterface EditorState {\n operationKey: string;\n current: ConnectorMappingTranslationState;\n}\n\n/** Options for {@link useMappingTranslationEditor}. */\nexport interface UseMappingTranslationEditorOptions {\n service: IConnectorMappingService;\n\n /** `undefined` before a connector is selected; every action no-ops until then. */\n connectorId: string | undefined;\n\n moduleKey: string;\n\n /** The rows the editor looks a row up in — pass `useConnectorMappings`' `mappings` straight in. */\n mappings: ConnectorMappingItem[];\n\n /**\n * The write. Pass `useConnectorMappingMutations`' `updateMapping`, which is what routes the save\n * through the translation-preserving fill and reports it to `onMappingSaved`.\n */\n updateMapping: (args: ConnectorMappingWriteArgs, callbacks?: MappingWriteCallbacks) => void;\n}\n\n/** Return shape of {@link useMappingTranslationEditor}. */\nexport interface UseMappingTranslationEditorResult {\n /** The row being edited, or `undefined` when the editor is closed or the row vanished. */\n editorRow: ConnectorMappingItem | undefined;\n\n /** True when a row is being edited. Gate the dialog's mount on this, not on `editorRow`. */\n isOpen: boolean;\n\n /** Wire to `ConnectorMappingViewProps.onEditTranslation` — the per-row settings icon. */\n onEditTranslation: (operationKey: string, current: ConnectorMappingTranslationState) => void;\n\n /** Wire to `MappingCompletenessBannerProps.onOpenMappingEditor`. */\n onOpenMappingEditor: (\n operation: MappingCompletenessItem,\n seed?: ConnectorMappingTranslationState,\n ) => void;\n\n /** Wire to `MappingCompletenessBannerProps.onSuggestWithAi`. */\n onSuggestWithAi: (operation: MappingCompletenessItem) => void;\n\n /** Wire to `MappingCompletenessBannerProps.suggestingOperationKey` — drives the per-row spinner. */\n suggestingOperationKey: string | null;\n\n /**\n * Ready-to-spread props for `ConnectorMappingTranslationEditorView`, valid only while\n * {@link isOpen}. Mount the dialog conditionally: the component initialises its inputs lazily on\n * each mount, which is what keeps an operator's in-flight edits sovereign against a concurrent\n * refresh of the row.\n */\n editorProps: ConnectorMappingTranslationEditorProps;\n}\n\n/** The failure shape the editor's Test panel renders when there is nothing to test against. */\nfunction testUnavailable(error: string): TestToolMappingResult {\n return { success: false, error, rawResponseJson: null, extractedFields: [] };\n}\n\n/**\n * The translation editor's state machine: which row is open, what it was seeded with, and the three\n * ways it can be opened.\n *\n * This was identical in all three plugins that had a Tool Mappings page, down to the comments, and\n * it needs nothing plugin-specific — no routing, no identity, no persistence. It is therefore the\n * part of the page hook that could move wholesale, and the part where a fourth copy would be pure\n * loss.\n *\n * The three entry points and why they differ:\n *\n * - **`onEditTranslation`** — the per-row icon. The view already holds the row's persisted\n * translation and passes it, so this seeds from the argument.\n * - **`onOpenMappingEditor`** — the completeness banner's \"Open mapping editor\". The banner holds a\n * completeness item, not a mapping row, so this looks the row up and seeds from it.\n * - **`onSuggestWithAi`** — the banner's AI assist. Seeds from the model's PROPOSAL for the two\n * field maps, while keeping the row's persisted `staticRequestFields`: the advisor reasons about\n * canonical field names and has no opinion on a pinned discriminator, so overwriting it with\n * nothing would silently drop a static parameter the operator set by hand.\n *\n * @remarks\n * **Save re-issues the write with the row's EXISTING tool name and row version**, which the host\n * treats as a normal set that leaves the tool selection alone. That route is also why\n * `onMappingSaved` belongs on the mutation and not on a page's click handlers: saving field\n * translations is a save, and it was the one of the three save routes originally missed.\n */\nexport function useMappingTranslationEditor({\n service,\n connectorId,\n moduleKey,\n mappings,\n updateMapping,\n}: UseMappingTranslationEditorOptions): UseMappingTranslationEditorResult {\n const [editor, setEditor] = useState<EditorState | null>(null);\n\n // An open draft belongs to the connector and module it was opened against. Left alone, switching\n // scope with the dialog open keeps that draft on screen, and `onSave` then re-issues it with the\n // NEW row's tool name and row version — writing one connector's field translation onto another's\n // mapping, silently and with no failure. Closing is the safe resolution: the operator loses an\n // unsaved draft they are no longer looking at, rather than saving it somewhere it does not\n // belong.\n const scopeKey = `${connectorId ?? \"\"}|${moduleKey}`;\n const editorScopeRef = useRef<string>(scopeKey);\n useEffect(() => {\n if (editorScopeRef.current === scopeKey) return;\n editorScopeRef.current = scopeKey;\n setEditor(null);\n }, [scopeKey]);\n const suggest = useSuggestMappingTranslation(service);\n const test = useTestMapping(service);\n\n const onEditTranslation = useCallback(\n (operationKey: string, current: ConnectorMappingTranslationState) => {\n setEditor({ operationKey, current });\n },\n [],\n );\n\n const onCancel = useCallback(() => setEditor(null), []);\n\n const onOpenMappingEditor = useCallback(\n (operation: MappingCompletenessItem, seed?: ConnectorMappingTranslationState) => {\n const row = mappings.find((m) => m.operationKey === operation.operationKey);\n setEditor({\n operationKey: operation.operationKey,\n current:\n seed ?? {\n requestFieldMap: row?.requestFieldMap ?? null,\n responseFieldPaths: row?.responseFieldPaths ?? null,\n staticRequestFields: row?.staticRequestFields ?? null,\n },\n });\n },\n [mappings],\n );\n\n const onSuggestWithAi = useCallback(\n (operation: MappingCompletenessItem) => {\n if (!connectorId) return;\n const row = mappings.find((m) => m.operationKey === operation.operationKey);\n suggest.mutate(\n {\n connectorId,\n moduleKey,\n body: { operationKey: operation.operationKey, toolName: row?.mappedToolName ?? \"\" },\n },\n {\n onSuccess: (result) => {\n if (!result.success) return;\n onOpenMappingEditor(operation, {\n requestFieldMap: result.requestFieldMap,\n responseFieldPaths: result.responseFieldPaths,\n // Preserved, not proposed. See the remarks above.\n staticRequestFields: row?.staticRequestFields ?? null,\n });\n },\n },\n );\n },\n // `moduleKey` is a real dependency, not a module-scope constant: omitting it would capture\n // whichever module was selected when this callback was created, so a multi-module consumer that\n // switched module and then asked for a suggestion would get advice on the previous catalogue.\n [connectorId, moduleKey, mappings, suggest, onOpenMappingEditor],\n );\n\n const onSave = useCallback(\n (\n requestFieldMap: Record<string, string> | null,\n responseFieldPaths: Record<string, string> | null,\n staticRequestFields: Record<string, string> | null,\n ) => {\n if (!editor) return;\n const row = mappings.find((m) => m.operationKey === editor.operationKey);\n if (!row || !row.mappedToolName) {\n // Defensive: the editor is not reachable for an unmapped row, but a row can vanish\n // mid-edit (a concurrent delete). Bail cleanly rather than writing a mapping with no tool,\n // which the host would reject anyway and report as a failed save the operator cannot act on.\n setEditor(null);\n return;\n }\n updateMapping({\n operationKey: row.operationKey,\n toolName: row.mappedToolName,\n rowVersion: row.rowVersion ?? \"\",\n // Passed through verbatim, INCLUDING nulls. The editor delivers `null` for a side whose\n // every input the operator emptied, and that is an explicit \"clear this\" the fill-in in\n // `useConnectorMappingMutations` must not overwrite.\n requestFieldMap,\n responseFieldPaths,\n staticRequestFields,\n });\n setEditor(null);\n },\n [editor, mappings, updateMapping],\n );\n\n const onTestMapping = useCallback(\n async (\n request: {\n sampleArgs: Record<string, unknown>;\n requestFieldMap: Record<string, string> | null;\n responseFieldPaths: Record<string, string> | null;\n staticRequestFields?: Record<string, string> | null;\n },\n extras?: ConnectorMappingTranslationExtras,\n ): Promise<TestToolMappingResult> => {\n if (!editor || !connectorId) {\n return testUnavailable(\"No connector or operation selected.\");\n }\n const row = mappings.find((m) => m.operationKey === editor.operationKey);\n if (!row?.mappedToolName) {\n return testUnavailable(\"No tool mapped to this operation.\");\n }\n return test.mutateAsync({\n connectorId,\n moduleKey,\n body: {\n operationKey: editor.operationKey,\n toolName: row.mappedToolName,\n requestFieldMap: request.requestFieldMap,\n responseFieldPaths: request.responseFieldPaths,\n // The editor's DRAFT static params, not the persisted row's, so the dry-run exercises the\n // unsaved discriminator the operator is actually testing.\n staticRequestFields: request.staticRequestFields ?? null,\n sampleArgs: request.sampleArgs,\n // Only the members the editor showed; absent stays absent all the way to the wire.\n ...(extras?.requestTemplates !== undefined && { requestTemplates: extras.requestTemplates }),\n ...(extras?.responseItemFieldPaths !== undefined && {\n responseItemFieldPaths: extras.responseItemFieldPaths,\n }),\n },\n });\n },\n // Same reason as `onSuggestWithAi`: a stale `moduleKey` here would dry-run the edit against a\n // different catalogue and report the result as if it were this one's.\n [editor, connectorId, moduleKey, mappings, test],\n );\n\n const editorRow = editor\n ? mappings.find((m) => m.operationKey === editor.operationKey)\n : undefined;\n\n return {\n editorRow,\n isOpen: editor !== null,\n onEditTranslation,\n onOpenMappingEditor,\n onSuggestWithAi,\n suggestingOperationKey: suggest.isPending\n ? (suggest.variables?.body.operationKey ?? null)\n : null,\n editorProps: {\n // Always `true`: the consumer gates the MOUNT on `isOpen`, which is what makes the editor's\n // lazy state initialisation reseed from the row each time it opens.\n open: true,\n operationLabel: editorRow?.label,\n toolName: editorRow?.mappedToolName,\n canonicalRequestFields: editorRow?.canonicalRequestFields,\n canonicalResponseFields: editorRow?.canonicalResponseFields,\n requestFieldMap: editor?.current.requestFieldMap ?? null,\n responseFieldPaths: editor?.current.responseFieldPaths ?? null,\n staticRequestFields: editor?.current.staticRequestFields ?? null,\n onSave,\n onCancel,\n onTestMapping,\n },\n };\n}\n","import { useCallback, useEffect, useRef } from \"react\";\n\nimport type { ConnectorMappingItem } from \"./types\";\n\n/** Options for {@link useAutoMatchOnConnectorSwitch}. */\nexport interface UseAutoMatchOnConnectorSwitchOptions {\n connectorId: string | undefined;\n\n /**\n * The module the rows belong to. Part of the latch key, not decoration: one connector commonly\n * serves several modules, and without this the hook cannot tell \"already auto-matched this\n * scope\" from \"auto-matched a different module on the same connector\".\n */\n moduleKey: string;\n\n /** The rows as last fetched. Their emptiness is what decides whether to trigger. */\n mappings: ConnectorMappingItem[];\n\n isLoading: boolean;\n isAutoMatching: boolean;\n\n /** `dataUpdatedAt` from `useConnectorMappings` — the fetch-freshness gate. */\n dataUpdatedAt: number;\n\n triggerAutoMatch: () => void;\n\n /**\n * `true` when the consumer arrived with a connector ALREADY selected — a `?connectorId=` deep\n * link, or a restored/preselected choice. Seeds the switch timestamp to mount time.\n *\n * @remarks\n * The three plugins this was promoted from all carry a comment saying this seed exists \"so the\n * auto-match trigger effect still fires on the first fetch\". That is backwards, and the tests\n * make it plain: seeding raises the bar the fetch timestamp has to clear, so it can only ever\n * SUPPRESS a trigger, never cause one. A fresh fetch resolves after mount and clears it either\n * way.\n *\n * What it actually does is refuse data that predates the mount. React-query can serve a cached\n * entry immediately — the operator was on this page a moment ago, or another surface primed the\n * same key — and with the ref at zero that stale snapshot is enough to fire an auto-match against\n * a connector nobody has looked at since. Seeding says \"wait for a fetch that happened after I\n * arrived\", which is the same statement `markConnectorChanged` makes about a switch. Set it\n * whenever the connector was chosen before this hook mounted.\n */\n initiallySelected?: boolean;\n}\n\n/** Return shape of {@link useAutoMatchOnConnectorSwitch}. */\nexport interface UseAutoMatchOnConnectorSwitchResult {\n /**\n * Call this whenever the consumer changes the selected connector, from inside its own setter.\n * The hook cannot observe the change itself in time: it needs the timestamp recorded BEFORE the\n * new connector's fetch resolves.\n */\n markConnectorChanged: () => void;\n}\n\n/**\n * Runs auto-match once, automatically, when a freshly selected connector turns out to have nothing\n * mapped.\n *\n * Identical in all three plugins this was promoted from, comments included, and it is entirely\n * mechanical — no identity, no routing, no persistence — so it moves whole.\n *\n * The four gates, each of which was added in response to something:\n *\n * 1. **`dataUpdatedAt > connectorChangedAt`.** Only mappings fetched AFTER the latest switch are\n * inspected. Without it the previous connector's data briefly reads as empty mid-transition and\n * auto-match fires against the wrong connector's emptiness.\n * 2. **`autoMatchedRef !== connectorId`.** Once per connector. Auto-match that legitimately matches\n * nothing would otherwise re-fire on every refetch, forever.\n * 3. **Something already mapped.** Never overwrite an operator's existing choices. A connector with\n * any mapped tool is one a human has already been through.\n * 4. **Not loading, not already matching.** Obvious, and the reason the button used to appear to do\n * nothing when pressed during a fetch.\n */\nexport function useAutoMatchOnConnectorSwitch({\n connectorId,\n moduleKey,\n mappings,\n isLoading,\n isAutoMatching,\n dataUpdatedAt,\n triggerAutoMatch,\n initiallySelected = false,\n}: UseAutoMatchOnConnectorSwitchOptions): UseAutoMatchOnConnectorSwitchResult {\n const connectorChangedAtRef = useRef<number>(initiallySelected ? Date.now() : 0);\n\n // Latched on the (connector, module) PAIR, not the connector alone. The composed surface\n // supports switching module on the same page, and a connector commonly serves several of them —\n // sales has twelve. Keyed on the connector only, auto-matching module A then switching to module\n // B on the same connector left this ref equal to that connector, so B's first auto-match was\n // suppressed and the operator saw an unmapped module with no explanation.\n const autoMatchedRef = useRef<string | null>(null);\n const scopeKey = `${connectorId ?? \"\"}|${moduleKey}`;\n\n const markConnectorChanged = useCallback(() => {\n connectorChangedAtRef.current = Date.now();\n }, []);\n\n useEffect(() => {\n if (!connectorId) return;\n if (isLoading || isAutoMatching) return;\n if (dataUpdatedAt <= connectorChangedAtRef.current) return;\n if (autoMatchedRef.current === scopeKey) return;\n if (mappings.length > 0 && mappings.some((m) => m.mappedToolName)) return;\n autoMatchedRef.current = scopeKey;\n triggerAutoMatch();\n }, [connectorId, scopeKey, mappings, isLoading, isAutoMatching, dataUpdatedAt, triggerAutoMatch]);\n\n return { markConnectorChanged };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nconst STORAGE_KEY_PREFIX = \"cc-connector-mapping-banner-dismissed\";\n\n/** Return shape of {@link useMappingBannerDismissal}. */\nexport interface UseMappingBannerDismissalResult {\n dismissed: boolean;\n dismiss: () => void;\n\n /** Un-dismisses. Call it when the selected connector changes — see the remarks. */\n reset: () => void;\n}\n\n/**\n * Dismissal of the auto-match outcome banner, per `(organisation, module)` pair, in\n * `localStorage`.\n *\n * Byte-identical in all three plugins this was promoted from, including the storage key prefix — so\n * a plugin adopting this keeps every operator's existing dismissals rather than resurfacing a\n * banner they have already dealt with.\n *\n * @param organisationId - From the consumer's own identity source. Injected rather than read here:\n * one plugin gets it from `useHostIdentity`, two from a local `useAuth`, and importing either would\n * make this hook unusable in the other. While it is falsy — pre-auth, or still resolving — the hook\n * reports a stable un-dismissed state and ignores writes, so the consumer renders the banner once\n * identity arrives instead of persisting a dismissal against no tenant.\n *\n * @param moduleKey - Scoped on the module as well as the organisation, so \"dismissed in Repo\n * Monitoring\" does not silence the banner in Timesheets.\n *\n * @remarks\n * The consumer is expected to call {@link UseMappingBannerDismissalResult.reset} when the selected\n * connector changes. Dismissal is keyed on organisation and module but NOT on connector, and a\n * dismissal earned by one connector's banner should not pre-silence a different connector's in the\n * same module. Keying on the connector instead would leak an unbounded number of `localStorage`\n * entries, one per connector the operator ever looked at.\n */\n/**\n * Reads the stored dismissal for a scope. Shared by the state initialiser and the scope-change\n * resync so the two cannot answer differently, and so the no-storage fallback is written once.\n */\nfunction readDismissed(storageKey: string | null): boolean {\n if (!storageKey) return false;\n try {\n return localStorage.getItem(storageKey) === \"true\";\n } catch {\n // Private mode, or storage disabled. The page must still work, just without memory.\n return false;\n }\n}\n\nexport function useMappingBannerDismissal(\n organisationId: string | undefined,\n moduleKey: string,\n): UseMappingBannerDismissalResult {\n const storageKey = organisationId\n ? `${STORAGE_KEY_PREFIX}:${organisationId}:${moduleKey}`\n : null;\n\n const [dismissed, setDismissed] = useState<boolean>(() => readDismissed(storageKey));\n\n // Re-read when the SCOPE changes. The initialiser above runs once per mount, so without this a\n // module or organisation switch on a page that stays mounted keeps the previous scope's answer:\n // a module dismissed earlier hides a different module's banner, and an organisation switch\n // carries one tenant's UI state into the next. Guarded on the key rather than run every render so\n // a dismissal made in THIS scope is not immediately overwritten by a re-read.\n const appliedKeyRef = useRef<string | null>(storageKey);\n useEffect(() => {\n if (appliedKeyRef.current === storageKey) return;\n appliedKeyRef.current = storageKey;\n setDismissed(readDismissed(storageKey));\n }, [storageKey]);\n\n const dismiss = useCallback(() => {\n if (!storageKey) return;\n try {\n localStorage.setItem(storageKey, \"true\");\n } catch {\n // Ignored: the banner just will not stay dismissed past this session.\n }\n setDismissed(true);\n }, [storageKey]);\n\n const reset = useCallback(() => {\n if (!storageKey) return;\n try {\n localStorage.removeItem(storageKey);\n } catch {\n // Ignored, as above.\n }\n setDismissed(false);\n }, [storageKey]);\n\n return { dismissed, dismiss, reset };\n}\n","import { useCallback, useMemo } from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\n\nimport { useAuthenticatedQuery } from \"../platform-react/useAuthenticatedQuery\";\nimport { useMutation } from \"@tanstack/react-query\";\nimport type { IConnectorMappingService } from \"./IConnectorMappingService\";\nimport { MODULE_PROVIDER_KIND, type ModuleProvider, type ModuleProviderKind } from \"./types\";\n\n/** Query key for one module's provider selection. */\nexport function moduleProviderQueryKey(moduleKey: string) {\n return [\"connector-mappings\", \"module-provider\", moduleKey] as const;\n}\n\nexport interface UseModuleProviderOptions {\n service: IConnectorMappingService;\n moduleKey: string;\n\n /**\n * Called after a successful write, with the provider as stored. Lets a page invalidate its own\n * reads — the mappings list is keyed on the connector, so a provider change is a different\n * connector's rows.\n */\n onProviderChanged?: (provider: ModuleProvider) => void;\n}\n\nexport interface UseModuleProviderResult {\n /** The organisation's stored choice, or the unconfigured state while loading or unset. */\n provider: ModuleProvider;\n\n /**\n * The chosen connector id, or `\"\"` when the module is unconfigured or served by a plugin.\n *\n * Shaped to drop straight into `useConnectorToolMappings`'s `connectorId`, which is the whole\n * point of this hook: that surface already takes the selection as an input, so adopting the\n * organisation-wide answer is a change of SOURCE and not a change to the surface.\n */\n connectorId: string;\n\n isLoading: boolean;\n\n /** Point the module at a connector. Pass `\"\"` to clear the selection back to unconfigured. */\n selectConnector: (connectorId: string) => void;\n\n /** Point the module at another installed extension. */\n selectExtension: (extensionId: string) => void;\n\n isSaving: boolean;\n error?: unknown;\n}\n\nconst UNCONFIGURED: ModuleProvider = {\n targetKind: null,\n connectorId: null,\n targetExtensionId: null,\n rowVersion: null,\n};\n\n/**\n * Reads and writes WHICH PROVIDER serves a module, as the organisation's own stored answer.\n *\n * @remarks\n * **This replaces a browser-local selection, and that is the entire point.** Before the host had\n * somewhere to put it, each plugin kept the operator's connector choice in `localStorage` — so the\n * answer was per-device rather than per-organisation. One operator selected a provider, a colleague\n * still saw the old one, and the two never reconciled. Worse, the two mechanisms could disagree\n * about a module that a plugin now serves, which is the situation the whole provider-selection\n * concept exists to end.\n *\n * **A clear is a first-class outcome.** `selectConnector(\"\")` writes the unconfigured state rather\n * than silently doing nothing, because \"no provider chosen\" and \"chose a connector with no mappings\n * yet\" are different situations with different remedies — the host deliberately does not default\n * one to the other, and neither does this.\n *\n * **The row version is handled here so callers never see it.** It is read from the query, sent on\n * the write, and refreshed from the write's response. A caller that has to thread a concurrency\n * token through its own state is a caller that will eventually send a stale one.\n */\nexport function useModuleProvider({\n service,\n moduleKey,\n onProviderChanged,\n}: UseModuleProviderOptions): UseModuleProviderResult {\n const queryClient = useQueryClient();\n const queryKey = useMemo(() => moduleProviderQueryKey(moduleKey), [moduleKey]);\n\n const query = useAuthenticatedQuery<ModuleProvider>({\n queryKey,\n queryFn: () => service.getModuleProvider(moduleKey),\n enabled: !!moduleKey,\n meta: { errorTitle: \"Error loading the module's provider\" },\n });\n\n const provider = query.data ?? UNCONFIGURED;\n\n const save = useMutation({\n mutationFn: (next: {\n targetKind: ModuleProviderKind;\n connectorId: string | null;\n targetExtensionId: string | null;\n }) =>\n service.setModuleProvider(\n moduleKey,\n next.targetKind,\n next.connectorId,\n next.targetExtensionId,\n provider.rowVersion,\n ),\n meta: { errorTitle: \"Could not change the module's provider\" },\n onSuccess: (stored) => {\n // Seeded rather than invalidated, so the refreshed row version is in place before any\n // subsequent write reads it. An invalidate-only would leave the previous token in the cache\n // until the refetch landed, and a second quick change would be rejected as stale.\n queryClient.setQueryData(queryKey, stored);\n onProviderChanged?.(stored);\n },\n });\n\n const selectConnector = useCallback(\n (connectorId: string) =>\n save.mutate({\n // An empty id is the CLEAR, and it must reach the host as one rather than being dropped\n // here: leaving the previous selection in place is exactly the \"connector is stuck forever\"\n // behaviour this surface has had to fix once already.\n targetKind: MODULE_PROVIDER_KIND.Connector,\n connectorId: connectorId || null,\n targetExtensionId: null,\n }),\n [save],\n );\n\n const selectExtension = useCallback(\n (extensionId: string) =>\n save.mutate({\n targetKind: MODULE_PROVIDER_KIND.Plugin,\n connectorId: null,\n targetExtensionId: extensionId || null,\n }),\n [save],\n );\n\n return {\n provider,\n // Only a CONNECTOR selection produces a connector id. A module served by a plugin has no\n // connector, and reporting one would make the mapping surface fetch rows for a connector the\n // operator did not choose.\n connectorId:\n provider.targetKind === MODULE_PROVIDER_KIND.Connector ? (provider.connectorId ?? \"\") : \"\",\n isLoading: query.isLoading,\n selectConnector,\n selectExtension,\n isSaving: save.isPending,\n error: query.error ?? save.error ?? undefined,\n };\n}\n","/**\n * What a canonical operation resolves to, and what happens to its field translation when that\n * changes.\n *\n * The Tool Mappings surface used to have one answer: a connector's tool. The platform now also\n * lets a mapping target another installed application's MCP tool, so \"Projects reads employees\n * from the HR app\" and \"Projects reads them from Workday\" are the same configuration with a\n * different provider. That makes the target a two-part thing - WHO provides it, and WHICH of their\n * tools - and this module owns the rules that follow from it.\n *\n * Pure by design. No react, no react-query, no transport: every rule here is a function of its\n * arguments, so the rules that were previously comments inside a mutation hook are now things a\n * test can fail.\n */\nimport {\n MAPPING_TARGET_KIND,\n MAPPING_TARGET_KIND_WIRE,\n type ConnectorMappingTranslationState,\n type MappingTargetKind,\n type MappingTargetKindWire,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Wire vocabulary conversion\n// ---------------------------------------------------------------------------\n\n/**\n * The UI-side kind as the host spells it.\n *\n * The host persists this string VERBATIM and matches it by name and case against its own\n * `ToolMappingTargetKind`, so a differently-cased value is stored as something that matches neither\n * branch of the resolver nor the database CHECK constraint. Converting in one named function is\n * what keeps that from being retyped as a literal at a call site.\n */\nexport function toWireTargetKind(kind: MappingTargetKind): MappingTargetKindWire {\n return kind === MAPPING_TARGET_KIND.plugin\n ? MAPPING_TARGET_KIND_WIRE.plugin\n : MAPPING_TARGET_KIND_WIRE.connector;\n}\n\n/**\n * The host's kind as this surface spells it, or `null`.\n *\n * `null` covers two cases on purpose, because the UI does the same thing in both: the module has no\n * selection at all (the host returns a null `targetKind` for an unconfigured module, and\n * deliberately does NOT default it to `\"Connector\"`), or the stored string matched neither member.\n * An unrecognised value is NOT guessed into a kind - rendering a choice nobody made is the exact\n * failure the host's own null is there to avoid, and a guess here would reintroduce it one layer up.\n */\nexport function fromWireTargetKind(value: string | null | undefined): MappingTargetKind | null {\n if (value === MAPPING_TARGET_KIND_WIRE.connector) return MAPPING_TARGET_KIND.connector;\n if (value === MAPPING_TARGET_KIND_WIRE.plugin) return MAPPING_TARGET_KIND.plugin;\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// The target\n// ---------------------------------------------------------------------------\n\n/**\n * Who answers a module's operations: a connector to a third-party system, or another installed\n * application.\n *\n * The plugin arm carries the providing extension rather than inferring it from the tool name's\n * prefix. That is the host's requirement and its reasoning is worth keeping here: the router\n * resolves a plugin tool by SCANNING the enabled extensions for a name match, breaking a duplicate\n * module-key collision by lowest extension id. A scan result is not a choice, and this type is\n * about recording a choice.\n */\nexport type MappingProvider =\n | { kind: \"connector\"; connectorId: string }\n | { kind: \"plugin\"; targetExtensionId: string };\n\n/**\n * A provider plus the specific tool of theirs that answers one operation.\n *\n * `toolName` means different things per arm, and both are the host's shape rather than this\n * module's invention: a connector target stores the BARE vendor verb, because the\n * `integrations:{slug}:` prefix is a function of the connector and not of the row; a plugin target\n * stores the FULLY NAMESPACED name (`hr:get-employees-by-ids`), because a plugin tool has no slug\n * to prefix and already routes verbatim.\n *\n * `\"\"` is the unmap request, exactly as on the connector-only surface.\n */\nexport type MappingTarget = MappingProvider & { toolName: string };\n\n/**\n * The stored mapping a write is replacing, reduced to the parts that decide what happens to the\n * translation.\n *\n * Both provider ids are optional because a caller assembles this from a row plus whatever it knows\n * about where the row came from - the connector-scoped list read, for instance, returns rows that\n * do not repeat the connector id the caller passed to fetch them. Absent is treated as \"not the\n * same provider\"; see {@link isSameTarget} for why that direction is the safe one.\n */\nexport interface ExistingMappingTarget extends Partial<ConnectorMappingTranslationState> {\n operationKey: string;\n targetKind: MappingTargetKind;\n\n /** The tool currently mapped, or `null` when the operation is unmapped. */\n mappedToolName: string | null;\n\n connectorId?: string | null;\n targetExtensionId?: string | null;\n}\n\n/**\n * One mapping write, with every translation member decided.\n *\n * The three translation members are REQUIRED and nullable rather than optional, matching\n * `IConnectorMappingService.updateMapping` and for the same reason: on this surface an omitted\n * member and a null member both write NULL host-side, so a type that lets a member be omitted lets\n * a caller destroy a translation without saying so.\n *\n * The two provider ids ARE optional, and exactly one of them is ever present. Absent rather than\n * null: \"there is no connector here\" and \"clear the connector\" are different statements, and only\n * one of them is true of a plugin target.\n */\nexport interface MappingUpdate extends ConnectorMappingTranslationState {\n targetKind: MappingTargetKind;\n toolName: string;\n connectorId?: string;\n targetExtensionId?: string;\n}\n\n/**\n * Does the write land on the SAME tool the stored translation was written against?\n *\n * Identity is the whole target - kind, provider, and tool name - not the tool name alone, and that\n * is deliberately stricter than a literal reading of the rule it serves. A bare vendor verb is not\n * unique: `get_file_contents` names a different tool with a different parameter shape in every\n * connector that has one, so comparing names alone would carry a translation from one vendor's tool\n * onto another's and call it unchanged.\n *\n * Unknown provenance counts as a different target. When a caller supplies no provider id on\n * {@link ExistingMappingTarget}, this returns `false` and the translation is discarded. That is the\n * safe direction of an unavoidable choice: a discarded translation is visibly absent and the\n * operator re-enters it, a carried-over one is silently wrong and nothing on the page says so.\n */\nexport function isSameTarget(\n existing: ExistingMappingTarget | null | undefined,\n next: MappingTarget,\n): boolean {\n if (!existing) return false;\n if (existing.targetKind !== next.kind) return false;\n\n // An unmapped row has no tool for a translation to have been written against, so there is nothing\n // to preserve even when the provider matches.\n if (!existing.mappedToolName) return false;\n if (existing.mappedToolName !== next.toolName) return false;\n\n return next.kind === MAPPING_TARGET_KIND.connector\n ? existing.connectorId === next.connectorId\n : existing.targetExtensionId === next.targetExtensionId;\n}\n\n/** Arguments for {@link buildMappingUpdate}. */\nexport interface BuildMappingUpdateArgs {\n /** The stored mapping, or `null` when the operation has never been mapped. */\n existing: ExistingMappingTarget | null;\n\n /** Where the operation is being pointed now. */\n next: MappingTarget;\n\n /**\n * The translation the caller is EXPLICITLY setting, member by member.\n *\n * Read with `!== undefined`, never `??`, and the difference is the whole point:\n *\n * - a member left `undefined` is the caller not talking about it, and it is decided by the\n * target-change rule below;\n * - a member set to `null` is the caller SAYING clear it, and it survives untouched.\n *\n * `??` collapses those two into one, which is the defect this surface has shipped three times -\n * once as \"picking a tool wiped the operator's field mapping\", once as \"the editor could not\n * clear a side at all\", and once in each of three plugin repos.\n */\n translation?: Partial<ConnectorMappingTranslationState>;\n}\n\n/**\n * Builds the write for pointing one operation at a target, deciding what happens to its translation.\n *\n * Two rules, and they are separate because they answer different questions:\n *\n * 1. **A translation belongs to ONE tool.** When the target changes, `requestFieldMap`,\n * `responseFieldPaths` and `staticRequestFields` are DISCARDED rather than carried across. They\n * are a map onto one tool's parameter names, and against a different tool they do not degrade -\n * they silently mean something else. Switching provider necessarily changes the tool, so this\n * fires on every provider switch and not only on a tool edit within a provider.\n *\n * 2. **Omitted is not cleared.** Whatever the caller states in `translation` wins, including an\n * explicit `null`. An operator supplying a translation for the new tool in the same action is\n * not fighting rule 1; rule 1 exists because nobody said anything about the translation, and\n * here somebody did.\n *\n * The order matters and is asserted by the tests: an explicit member beats the discard, and the\n * discard beats the carry-forward.\n */\nexport function buildMappingUpdate({\n existing,\n next,\n translation,\n}: BuildMappingUpdateArgs): MappingUpdate {\n const sameTarget = isSameTarget(existing, next);\n\n const decide = (\n member: keyof ConnectorMappingTranslationState,\n ): Record<string, string> | null => {\n const supplied = translation?.[member];\n // Rule 2. `!== undefined`, never `??`: `null` here is an instruction, not an absence.\n if (supplied !== undefined) return supplied;\n\n // Rule 1. A different tool means the stored translation describes something else.\n if (!sameTarget) return null;\n\n return existing?.[member] ?? null;\n };\n\n const update: MappingUpdate = {\n targetKind: next.kind,\n toolName: next.toolName,\n requestFieldMap: decide(\"requestFieldMap\"),\n responseFieldPaths: decide(\"responseFieldPaths\"),\n staticRequestFields: decide(\"staticRequestFields\"),\n };\n\n // Exactly one provider id, and the other key is ABSENT rather than null. The host's write\n // commands are split per target kind and each refuses a payload carrying the other half, so\n // sending `connectorId: null` on a plugin write is not a tidier way of saying nothing - it is a\n // field the endpoint has no slot for.\n if (next.kind === MAPPING_TARGET_KIND.connector) {\n update.connectorId = next.connectorId;\n } else {\n update.targetExtensionId = next.targetExtensionId;\n }\n\n return update;\n}\n\n/**\n * The provider half of a target, for the per-module selection. Drops the tool, because the module\n * selection is a choice about WHO serves the module and every operation under it keeps its own tool.\n */\nexport function providerOf(target: MappingTarget): MappingProvider {\n return target.kind === MAPPING_TARGET_KIND.connector\n ? { kind: MAPPING_TARGET_KIND.connector, connectorId: target.connectorId }\n : { kind: MAPPING_TARGET_KIND.plugin, targetExtensionId: target.targetExtensionId };\n}\n\n/**\n * The provider's id, whichever half it is. For a caller that only needs to know whether the choice\n * changed, rather than which kind it is.\n */\nexport function providerIdOf(provider: MappingProvider): string {\n return provider.kind === MAPPING_TARGET_KIND.connector\n ? provider.connectorId\n : provider.targetExtensionId;\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type { MappingCompletenessBannerProps } from \"../components/connectors/MappingCompletenessBanner\";\nimport type { ConnectorMappingTranslationEditorProps } from \"../components/connectors/ConnectorMappingTranslationEditorView\";\nimport type { MappingReadiness } from \"../components/connectors/mappingCompleteness\";\nimport type { MappingCompletenessItem } from \"../components/connectors/types\";\nimport type { ConnectorOption, ConnectorToolOption } from \"./connectorOptions\";\nimport type { IConnectorMappingService } from \"./IConnectorMappingService\";\nimport { fromWireTargetKind } from \"./targetSelection\";\nimport { useModuleProvider } from \"./useModuleProvider\";\nimport { useAutoMatchOnConnectorSwitch } from \"./useAutoMatchOnConnectorSwitch\";\nimport {\n useConnectorMappingCompleteness,\n useConnectorMappings,\n} from \"./useConnectorMappings\";\nimport { useConnectorMappingMutations } from \"./useConnectorMappingMutations\";\nimport { useMappingBannerDismissal } from \"./useMappingBannerDismissal\";\nimport { useMappingTranslationEditor } from \"./useMappingTranslationEditor\";\nimport {\n MAPPING_TARGET_KIND,\n type ConnectorMappingItem,\n type ConnectorMappingTranslationState,\n type MappingTargetKind,\n type PluginProviderCandidate,\n} from \"./types\";\n\n/** Options for {@link useConnectorToolMappings}. */\nexport interface UseConnectorToolMappingsOptions {\n /** Mapping transport. See `ConnectorMappingService` for the default implementation. */\n service: IConnectorMappingService;\n\n // --- the selected pair -----------------------------------------------------\n\n /** `\"\"` or `undefined` before the operator has chosen one. */\n connectorId: string | undefined;\n\n /**\n * Selected module. A plain input, so a consumer with ONE catalogue passes a module-scope constant\n * and a consumer with twelve passes whichever the operator selected — every query key, the\n * invalidation, the auto-match trigger and the banner dismissal re-key with it.\n */\n moduleKey: string;\n\n /** Human label for the module, for the view's own header and empty state. */\n moduleLabel: string;\n\n // --- data the consumer owns ------------------------------------------------\n\n /**\n * The connectors to offer. Injected because connector enumeration differs genuinely between\n * consumers — one reads `settings:list-connectors`, one pages a full connector CRUD service, one\n * has no source yet — and none of those belongs in a mapping hook. `mapConnectorsToOptions`\n * projects the default service's shape.\n */\n connectors: ConnectorOption[];\n\n /**\n * Whether the CONNECTORS query is still in flight. Folded into the view's `isLoading` because the\n * mappings query is disabled until a connector is chosen and therefore reports `isLoading: false`\n * on first load — which made the view skip its skeleton and render the picker as an EMPTY\n * dropdown while the connector list was still loading. It read as \"this organisation has no\n * connectors\", and then they appeared.\n */\n isConnectorsLoading?: boolean;\n\n /** The selected connector's mappable tools. `mapConnectorToolsToOptions` projects them. */\n availableTools: ConnectorToolOption[];\n\n /**\n * The consumer's connector setter — bare `setState`, a persisting setter, a URL write, whatever\n * it owns. Wrapped here so the auto-match trigger learns of the switch; the consumer wires the\n * returned `view.onConnectorChange` to its picker and never calls its own setter directly.\n */\n onConnectorChange: (connectorId: string) => void;\n\n /** `true` when a connector was already selected on mount (deep link / restored / preselected). */\n initiallySelected?: boolean;\n\n // --- provider selection ----------------------------------------------------\n //\n // All optional. Omit them and this hook behaves exactly as it did when a connector was the only\n // thing that could serve a module: no extra query, no extra view props, nothing rendered.\n\n /**\n * Opts this hook into the per-module provider choice: it reads and writes the organisation's\n * stored provider through {@link useModuleProvider} on the `service` already passed above, and\n * hands the view the picker's props.\n *\n * Defaults to `false`, and the default is the load-bearing part. The transport exists -\n * `connectors:mappings:get-provider` and `connectors:mappings:set-provider` are real tools - but\n * only on a host that has them DEPLOYED, and this package is pinned independently by every\n * plugin. Turning the read on for every existing consumer would fire a tool call an older host\n * answers with an error toast, on a surface that worked. A consumer opts in when its host is\n * ready, and adds the two tool names to its `useToolInvokerMap`.\n */\n providerSelectionEnabled?: boolean;\n\n /**\n * The installed applications on offer, with the tools each can answer this module's operations\n * with.\n *\n * An INPUT rather than something this hook fetches, matching {@link connectors} and for the same\n * reason: the candidate set is a server-side authorisation decision (see\n * {@link PluginProviderCandidate}), and no consumer should be able to widen it by filtering\n * client-side. Supply it from whatever surface the host eventually exposes - there is no\n * candidate-list endpoint in any transport today, which is why this cannot be fetched here.\n *\n * Left empty, the app arm of the picker has nothing to offer, so the view renders the connector\n * arm only.\n */\n pluginProviders?: PluginProviderCandidate[];\n\n /** Whether the candidate list is still in flight. Folded into the view's `isLoading`. */\n isPluginProvidersLoading?: boolean;\n\n // --- injected environment --------------------------------------------------\n\n /**\n * Current organisation, for the per-tenant banner dismissal. Injected because identity differs:\n * one plugin reads `useHostIdentity`, two read a local `useAuth`. Absent is handled — dismissal\n * simply does not persist until identity resolves.\n */\n organisationId?: string;\n\n /** Href of the host's AI-providers settings page. Routing is never owned here. */\n aiProvidersHref: string;\n\n /** Href of the selected connector's settings page, already scoped to `connectorId`. */\n connectorSettingsHref: string;\n\n /** Passed through to the view. See `ConnectorMappingViewProps.withHeader`. */\n withHeader?: boolean;\n\n /**\n * Called after any save. See `useConnectorMappingMutations` — this is the hook a consumer needs\n * in order to mirror the connector into its own settings, without which a background worker\n * cannot find a connector at all.\n */\n onMappingSaved?: (connectorId: string, moduleKey: string) => void;\n\n /** Success-toast sink. Defaults to the SDK's host-event toast. */\n showSuccessToast?: (title: string, description?: string) => void;\n}\n\n/**\n * Props for `ConnectorMappingView`, minus nothing — spread this straight onto it.\n *\n * Declared structurally rather than imported from the component so this module stays free of\n * `@mui/material`: a consumer can take the hooks without installing MUI, and the view's own props\n * type is assignable to this.\n */\nexport interface ConnectorMappingViewBundle {\n isLoading: boolean;\n moduleKey: string;\n moduleLabel: string;\n withHeader?: boolean;\n connectors: ConnectorOption[];\n selectedConnectorId: string;\n onConnectorChange: (connectorId: string) => void;\n\n // Present only when the consumer set `providerSelectionEnabled`; absent otherwise, which is what\n // keeps the view rendering its pre-provider shape for an un-adopted consumer.\n providerKind?: MappingTargetKind;\n onProviderKindChange?: (kind: MappingTargetKind) => void;\n pluginProviders?: { extensionId: string; name: string }[];\n selectedPluginExtensionId?: string;\n onPluginProviderChange?: (extensionId: string) => void;\n\n mappings: ConnectorMappingItem[];\n availableTools: ConnectorToolOption[];\n onUpdateMapping: (operationKey: string, toolName: string, rowVersion: string) => void;\n onAutoMatch: () => void;\n isUpdating: boolean;\n isAutoMatching: boolean;\n bannerState: ReturnType<typeof useConnectorMappingMutations>[\"autoMatchSummary\"];\n onDismissBanner: () => void;\n aiProvidersHref: string;\n connectorSettingsHref: string;\n onEditTranslation: (operationKey: string, current: ConnectorMappingTranslationState) => void;\n}\n\n/** Return shape of {@link useConnectorToolMappings}. */\nexport interface UseConnectorToolMappingsResult {\n /** Spread onto `ConnectorMappingView`. */\n view: ConnectorMappingViewBundle;\n\n /**\n * Spread onto `MappingCompletenessBanner`.\n *\n * Render it whenever `banner.isProviderUnconfigured` is true, and otherwise only when a provider\n * is selected. Gating the mount on a selected CONNECTOR - which the pre-provider guidance here\n * said, because a connector was the only provider there was - would hide the one message that\n * tells an operator how to select one.\n */\n banner: MappingCompletenessBannerProps;\n\n /** Spread onto `ConnectorMappingTranslationEditorView`. Mount it only when `isEditorOpen`. */\n editor: ConnectorMappingTranslationEditorProps;\n\n /** Gate the editor dialog's MOUNT on this, so it reseeds from the row each time it opens. */\n isEditorOpen: boolean;\n\n /** The row the editor is open for, or `undefined`. Exposed for a consumer's own render guards. */\n editorRow: ConnectorMappingItem | undefined;\n\n /** The rows, for a consumer that renders something of its own alongside the table. */\n mappings: ConnectorMappingItem[];\n\n /** Per-operation completeness, for a consumer that wants it outside the banner. */\n completenessOperations: MappingCompletenessItem[];\n\n /** Readiness over REQUIRED operations only — for a progress indicator or a \"ready\" badge. */\n readiness: MappingReadiness;\n\n /** True while either query is loading, connectors included. */\n isLoading: boolean;\n}\n\n/**\n * The whole Tool Mappings surface for one connector + module, assembled from the parts in this\n * module.\n *\n * ## What this is, and what it deliberately is not\n *\n * Three plugins each had a ~300-line page hook, and roughly 85% of each was identical. This is that\n * 85%: the reads, the writes, the translation editor, the auto-match trigger, the banner dismissal,\n * and the three prop bundles a page spreads. What is NOT here is the 15% that genuinely differs —\n * **which** connector and module are selected, where the selection is remembered, where identity\n * comes from, how hrefs are built, and how connectors are enumerated. Those arrive as inputs.\n *\n * That split is not a compromise, it is the finding: every one of those five is a real difference\n * between the three consumers, and every attempt to absorb one would have imported a plugin's\n * router, auth module or connector service into this package. A consumer's remaining page hook is\n * the selection logic and nothing else.\n *\n * ## Multi-module is not a special case\n *\n * `moduleKey` and `moduleLabel` are ordinary inputs, so a page with a module selector re-renders\n * this hook with the new key and everything downstream re-keys — including the auto-match trigger,\n * which will then evaluate the new module's emptiness rather than the old one's. A page with one\n * module passes a constant and pays nothing.\n *\n * @example Single-module consumer\n * ```ts\n * const surface = useConnectorToolMappings({\n * service,\n * connectorId,\n * moduleKey: MODULE_KEY,\n * moduleLabel: \"Repo Monitoring\",\n * connectors: mapConnectorsToOptions(connectors),\n * isConnectorsLoading,\n * availableTools: mapConnectorToolsToOptions(connectorDetail?.tools ?? []),\n * onConnectorChange: setConnectorId,\n * organisationId: user?.organisationId,\n * aiProvidersHref: routes.settings.aiProviders.root(),\n * connectorSettingsHref: connectorId\n * ? routes.settings.connectors.details(connectorId)\n * : routes.settings.connectors.root(),\n * });\n * ```\n */\nexport function useConnectorToolMappings({\n service,\n connectorId,\n moduleKey,\n moduleLabel,\n connectors,\n isConnectorsLoading = false,\n availableTools,\n onConnectorChange,\n initiallySelected = false,\n organisationId,\n aiProvidersHref,\n connectorSettingsHref,\n withHeader,\n onMappingSaved,\n showSuccessToast,\n providerSelectionEnabled = false,\n pluginProviders,\n isPluginProvidersLoading = false,\n}: UseConnectorToolMappingsOptions): UseConnectorToolMappingsResult {\n // Gated by passing an empty module key, which is the hook's own documented \"nothing to read\"\n // condition (`enabled: !!moduleKey`), rather than by a second switch on its options. A hook\n // cannot be called conditionally, and adding a parameter to a surface that shipped days ago\n // purely to say what an existing one already says would be two ways to express one state.\n const {\n provider: storedProvider,\n connectorId: storedConnectorId,\n isLoading: isProviderLoading,\n selectConnector,\n selectExtension,\n } = useModuleProvider({\n service,\n moduleKey: providerSelectionEnabled ? moduleKey : \"\",\n });\n\n // The kind the operator has picked but not yet completed, because a kind alone is half a choice\n // and the host refuses to store one: a connector choice must name a connector, a plugin choice an\n // extension. So switching the kind is local intent, and the write happens when they pick the\n // concrete provider under it. Without this the \"Served by\" control would appear not to respond.\n const [pendingKind, setPendingKind] = useState<MappingTargetKind | null>(null);\n\n // Intent belongs to the module it was expressed for. A multi-module page that switched module\n // would otherwise carry the previous module's half-made choice onto a module the operator has not\n // looked at, and the first provider they picked would be stored against the wrong one.\n const pendingKindModuleRef = useRef(moduleKey);\n if (pendingKindModuleRef.current !== moduleKey) {\n pendingKindModuleRef.current = moduleKey;\n if (pendingKind !== null) setPendingKind(null);\n }\n\n // The stored kind, converted from the host's vocabulary rather than compared against it. See\n // `fromWireTargetKind`: an unrecognised value becomes `null`, never a guessed kind.\n const storedKind = fromWireTargetKind(storedProvider.targetKind);\n const providerKind = providerSelectionEnabled\n ? (pendingKind ?? storedKind ?? undefined)\n : undefined;\n const isPluginMode = providerKind === MAPPING_TARGET_KIND.plugin;\n\n const selectedPluginExtensionId =\n storedKind === MAPPING_TARGET_KIND.plugin ? (storedProvider.targetExtensionId ?? \"\") : \"\";\n\n // With provider selection on, the ORGANISATION's stored answer wins over the id the consumer\n // passed. That is the whole point of the stored selection: the `connectorId` prop is fed from a\n // per-device source in every consumer today, so preferring it here would leave two answers to one\n // question and no rule for which is right. `storedConnectorId` is `\"\"` unless the stored kind\n // is Connector, so a plugin-served module contributes nothing to this line.\n const effectiveConnectorId = providerSelectionEnabled ? storedConnectorId : connectorId;\n\n // `\"\"` and `undefined` both mean \"nothing selected\"; normalise once so every hook below and the\n // view agree. Two of the three plugins carried `connectorId || undefined` at four call sites.\n //\n // Forced to undefined in plugin mode, which disables every connector-scoped read. Leaving the\n // previous connector's id in place would render THAT connector's mapping rows and completeness\n // under a module the operator has just pointed at an app - data that is real, and about something\n // else.\n const selectedConnectorId = isPluginMode ? undefined : effectiveConnectorId || undefined;\n const scope = { service, connectorId: selectedConnectorId, moduleKey };\n\n const { mappings, isLoading, dataUpdatedAt } = useConnectorMappings(scope);\n const { operations: completenessOperations, readiness } =\n useConnectorMappingCompleteness(scope);\n\n const { triggerAutoMatch, isAutoMatching, updateMapping, isUpdating, autoMatchSummary } =\n useConnectorMappingMutations({ ...scope, onMappingSaved, showSuccessToast });\n\n const editor = useMappingTranslationEditor({\n service,\n connectorId: selectedConnectorId,\n moduleKey,\n mappings,\n updateMapping,\n });\n\n const { dismissed, dismiss, reset } = useMappingBannerDismissal(organisationId, moduleKey);\n\n const { markConnectorChanged } = useAutoMatchOnConnectorSwitch({\n connectorId: selectedConnectorId,\n moduleKey,\n mappings,\n isLoading,\n isAutoMatching,\n dataUpdatedAt,\n triggerAutoMatch,\n initiallySelected,\n });\n\n const handleProviderKindChange = useCallback(\n (kind: MappingTargetKind) => {\n setPendingKind(kind);\n },\n [],\n );\n\n const handlePluginProviderChange = useCallback(\n (extensionId: string) => {\n selectExtension(extensionId);\n },\n [selectExtension],\n );\n\n const handleConnectorChange = useCallback(\n (id: string) => {\n onConnectorChange(id);\n markConnectorChanged();\n\n // Choosing a connector IS the connector half of the provider choice, so it is recorded here\n // rather than behind a second confirm. A separate \"save provider\" step would let an operator\n // pick a connector, map every operation against it, and still have the module resolve as\n // unconfigured - the mappings correct and the page inert.\n //\n // An empty id is passed through as the CLEAR, not swallowed: `selectConnector(\"\")` writes the\n // unconfigured state, which is the route back that this surface has already had to add once.\n if (providerSelectionEnabled) selectConnector(id);\n },\n // `onConnectorChange` is a real dependency: a consumer that persists per organisation rebinds\n // its setter once identity resolves, and an empty dep array would capture the pre-identity one\n // and stop writing through.\n [onConnectorChange, markConnectorChanged, providerSelectionEnabled, selectConnector],\n );\n\n const handleUpdateMapping = useCallback(\n (operationKey: string, toolName: string, rowVersion: string) => {\n if (!selectedConnectorId) return;\n // Note what is NOT passed: the three translation members. Omitting them is what tells\n // `useConnectorMappingMutations` to carry the row's existing translation forward. Passing\n // explicit nulls here is the production defect this module was extracted to end.\n updateMapping({ operationKey, toolName, rowVersion });\n },\n [selectedConnectorId, updateMapping],\n );\n\n const handleAutoMatch = useCallback(() => {\n if (!selectedConnectorId) return;\n triggerAutoMatch();\n }, [selectedConnectorId, triggerAutoMatch]);\n\n // A dismissal earned by one connector's banner must not pre-silence a different connector's in\n // the same module. Dismissal is keyed on (organisation, module) only — see the hook's remarks for\n // why adding the connector to the key would be worse.\n //\n // Guarded on an ACTUAL connector change, for two reasons that are both bugs when it is not:\n //\n // * It ran on MOUNT. `useMappingBannerDismissal` reads `true` from storage in its initialiser,\n // and this effect then immediately removed the entry and flipped it back to `false` — so a\n // dismissal never survived a reload or a navigation, and the stored one was destroyed on the\n // way. The banner came back every time, which reads as the dismiss button being broken.\n // * `reset` changes identity when the dismissal's own (organisation, module) key changes, so a\n // MODULE switch re-ran it and cleared the dismissal belonging to the module just switched TO —\n // defeating the resync that hook now does. Hence the module is compared too: when it changed,\n // the dismissal is a different scope's and is re-read rather than reset.\n const previousScopeRef = useRef<{ connectorId?: string; moduleKey: string }>({\n connectorId: selectedConnectorId || undefined,\n moduleKey,\n });\n useEffect(() => {\n const previous = previousScopeRef.current;\n const current = { connectorId: selectedConnectorId || undefined, moduleKey };\n previousScopeRef.current = current;\n\n if (previous.moduleKey !== current.moduleKey) return;\n if (previous.connectorId === current.connectorId) return;\n\n reset();\n }, [selectedConnectorId, moduleKey, reset]);\n\n // In plugin mode the tools on offer are the CHOSEN app's, not the connector's. Derived here\n // rather than asked of the consumer so a page cannot supply one provider's tools against another\n // provider's operations - a picker full of names that resolve to nothing.\n const effectiveAvailableTools = useMemo<ConnectorToolOption[]>(() => {\n if (!isPluginMode) return availableTools;\n const chosen = (pluginProviders ?? []).find(\n (p) => p.extensionId === selectedPluginExtensionId,\n );\n return (chosen?.tools ?? []).map((t) => ({\n name: t.toolName,\n description: t.description ?? \"\",\n }));\n }, [isPluginMode, availableTools, pluginProviders, selectedPluginExtensionId]);\n\n const providerOptions = useMemo(\n () => (pluginProviders ?? []).map((p) => ({ extensionId: p.extensionId, name: p.name })),\n [pluginProviders],\n );\n\n const surfaceIsLoading =\n isLoading || isConnectorsLoading || isProviderLoading || isPluginProvidersLoading;\n\n return {\n view: {\n isLoading: surfaceIsLoading,\n moduleKey,\n moduleLabel,\n withHeader,\n connectors,\n selectedConnectorId: selectedConnectorId ?? \"\",\n onConnectorChange: handleConnectorChange,\n // Spread conditionally: for a consumer that has not opted in these keys are ABSENT, not\n // undefined, so the view's `providerPickerEnabled` check sees exactly what a pre-provider\n // consumer passes.\n ...(providerSelectionEnabled\n ? {\n providerKind,\n onProviderKindChange: handleProviderKindChange,\n pluginProviders: providerOptions,\n selectedPluginExtensionId,\n onPluginProviderChange: handlePluginProviderChange,\n }\n : {}),\n mappings,\n availableTools: effectiveAvailableTools,\n onUpdateMapping: handleUpdateMapping,\n onAutoMatch: handleAutoMatch,\n isUpdating,\n isAutoMatching,\n bannerState: dismissed ? null : autoMatchSummary,\n onDismissBanner: dismiss,\n aiProvidersHref,\n connectorSettingsHref,\n onEditTranslation: editor.onEditTranslation,\n },\n banner: {\n operations: completenessOperations,\n onSuggestWithAi: editor.onSuggestWithAi,\n onOpenMappingEditor: editor.onOpenMappingEditor,\n suggestingOperationKey: editor.suggestingOperationKey,\n // False whenever the consumer has not opted in, not merely when a choice exists. A page with\n // no provider control on it must not be told it has failed to use one. Also false while the\n // read is in flight, so the banner does not accuse the operator during a page load.\n isProviderUnconfigured:\n providerSelectionEnabled && !isProviderLoading && storedKind === null,\n },\n editor: editor.editorProps,\n isEditorOpen: editor.isOpen,\n editorRow: editor.editorRow,\n mappings,\n completenessOperations,\n readiness,\n isLoading: surfaceIsLoading,\n };\n}\n","import type { ConnectorSummary, ConnectorToolSummary } from \"./types\";\n\n/** A connector as the mapping picker renders it. */\nexport interface ConnectorOption {\n id: string;\n name: string;\n}\n\n/** A tool as the per-row mapping autocomplete renders it. */\nexport interface ConnectorToolOption {\n name: string;\n description: string;\n}\n\n/**\n * Project the organisation's connectors down to the picker's option shape.\n *\n * @remarks\n * **Deliberately unfiltered.** One plugin filtered this list to MCP connectors, because its\n * connector service returned every type; another reads `settings:list-connectors`, whose projection\n * does not carry a connector type at all, so the same filter compared against `undefined` and hid\n * every connector — a picker that is empty for a reason no operator could work out. The host\n * already scopes the list to the current organisation, and a mapping against a connector that\n * cannot serve it fails loudly at Test. A consumer whose own service returns types it must exclude\n * should filter BEFORE calling this, where it can name what it is excluding and why.\n */\nexport function mapConnectorsToOptions(\n connectors: readonly ConnectorSummary[],\n): ConnectorOption[] {\n return connectors.map((c) => ({ id: c.id, name: c.name }));\n}\n\n/**\n * Project a connector's tools down to the mapping autocomplete's option shape.\n *\n * Disabled tools are dropped: an operator can only map an operation to a tool the connector is\n * currently exposing, and offering a switched-off one produces a mapping that resolves to nothing\n * at runtime. The service returns them unfiltered on purpose — see\n * `ConnectorMappingService.listConnectorTools` — so the distinction stays visible one layer down.\n */\nexport function mapConnectorToolsToOptions(\n tools: readonly ConnectorToolSummary[],\n): ConnectorToolOption[] {\n return tools\n .filter((t) => t.isEnabled)\n .map((t) => ({ name: t.toolName, description: t.description ?? \"\" }));\n}\n","/**\n * The words the provider controls use, in one place, because two surfaces have to say the same ones.\n *\n * ## Why these are constants and not literals at each site\n *\n * A \"not configured\" message has to name the control that resolves it, in that control's own words.\n * This surface has already paid for the alternative: the completeness banner described a gap in the\n * abstract (\"the mapping editor\"), an operator could not find any such thing on the page, and the\n * report that came back was \"it doesnt really suggest how to fix it\". The fix was to name the\n * affordance verbatim - and a verbatim name held as two separate string literals is one rename away\n * from pointing at nothing again, with both files still compiling and both tests still green.\n *\n * So the control renders {@link PROVIDER_CONTROL_LABEL} and the banner quotes it, from here. A test\n * asserts the banner's text contains the label the view actually renders; it fails if either side\n * is edited alone.\n *\n * Pure strings, no imports. That matters: the mapping HOOKS module value-imports this, and it must\n * not drag `@mui/material` into a barrel whose consumers may not have installed it.\n */\n\n/** Label of the control that chooses what serves a module. */\nexport const PROVIDER_CONTROL_LABEL = \"Served by\";\n\n/**\n * Labels of the two things that can serve a module.\n *\n * Phrased from the operator's side rather than the schema's. \"Plugin\" and \"Connector\" are the\n * platform's words for its own internals; an operator choosing where a module's data comes from is\n * choosing between the applications they already run and something outside.\n */\nexport const PROVIDER_OPTION_LABEL = {\n plugin: \"This organisation's apps\",\n connector: \"External system\",\n} as const;\n\n/** Label of the control that picks WHICH installed application, once \"this organisation's apps\" is chosen. */\nexport const PLUGIN_PROVIDER_CONTROL_LABEL = \"App\";\n\n/** Label of the control that picks WHICH connector, once \"external system\" is chosen. */\nexport const CONNECTOR_CONTROL_LABEL = \"Connector\";\n"]}
|