@jini-ai/ui 0.4.3 → 0.4.5

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.
Files changed (169) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +20 -0
  3. package/dist/core/theme/default.d.ts +4 -0
  4. package/dist/core/theme/default.js +11 -0
  5. package/dist/core/theme/stylesheet.d.ts +8 -0
  6. package/dist/core/theme/stylesheet.js +14 -0
  7. package/dist/core/theme/types.d.ts +39 -0
  8. package/dist/core/theme/types.js +2 -0
  9. package/dist/core/theme/validation.d.ts +15 -0
  10. package/dist/core/theme/validation.js +55 -0
  11. package/dist/features/admin-widgets/components/Select/Select.d.ts +11 -3
  12. package/dist/features/admin-widgets/components/Select/Select.hooks.d.ts +8 -13
  13. package/dist/features/admin-widgets/components/Select/Select.hooks.js +37 -215
  14. package/dist/features/admin-widgets/components/Select/Select.js +4 -3
  15. package/dist/features/appearance/constants.js +1 -1
  16. package/dist/features/connectors/rules.d.ts +1 -6
  17. package/dist/features/connectors/types.d.ts +1 -6
  18. package/dist/features/connectors/types.js +1 -6
  19. package/dist/features/execution/http-adapter.d.ts +111 -0
  20. package/dist/features/execution/http-adapter.js +160 -0
  21. package/dist/features/execution/index.d.ts +4 -0
  22. package/dist/features/execution/index.js +2 -0
  23. package/dist/features/execution/react/hooks/use-execution-credential.hooks.d.ts +232 -0
  24. package/dist/features/execution/react/hooks/use-execution-credential.hooks.js +217 -0
  25. package/dist/features/html-editor/canvas-style.d.ts +1 -1
  26. package/dist/features/html-editor/canvas-style.js +2 -2
  27. package/dist/features/i18n/dictionaries/settings-dialog.ar.js +2 -0
  28. package/dist/features/i18n/dictionaries/settings-dialog.bn.js +2 -0
  29. package/dist/features/i18n/dictionaries/settings-dialog.de.js +2 -0
  30. package/dist/features/i18n/dictionaries/settings-dialog.en.js +2 -0
  31. package/dist/features/i18n/dictionaries/settings-dialog.es.js +2 -0
  32. package/dist/features/i18n/dictionaries/settings-dialog.fa.js +2 -0
  33. package/dist/features/i18n/dictionaries/settings-dialog.fr.js +2 -0
  34. package/dist/features/i18n/dictionaries/settings-dialog.hi.js +2 -0
  35. package/dist/features/i18n/dictionaries/settings-dialog.hu.js +2 -0
  36. package/dist/features/i18n/dictionaries/settings-dialog.id.js +2 -0
  37. package/dist/features/i18n/dictionaries/settings-dialog.it.js +2 -0
  38. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +2 -0
  39. package/dist/features/i18n/dictionaries/settings-dialog.ko.js +2 -0
  40. package/dist/features/i18n/dictionaries/settings-dialog.pl.js +2 -0
  41. package/dist/features/i18n/dictionaries/settings-dialog.pt-BR.js +2 -0
  42. package/dist/features/i18n/dictionaries/settings-dialog.ru.js +2 -0
  43. package/dist/features/i18n/dictionaries/settings-dialog.th.js +2 -0
  44. package/dist/features/i18n/dictionaries/settings-dialog.tr.js +2 -0
  45. package/dist/features/i18n/dictionaries/settings-dialog.uk.js +2 -0
  46. package/dist/features/i18n/dictionaries/settings-dialog.ur.js +2 -0
  47. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +2 -0
  48. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +2 -0
  49. package/dist/features/i18n/dictionary-translator.d.ts +5 -2
  50. package/dist/features/i18n/dictionary-translator.js +1 -1
  51. package/dist/features/i18n/index.d.ts +1 -1
  52. package/dist/features/i18n/index.js +1 -1
  53. package/dist/features/i18n/template-i18n.d.ts +11 -0
  54. package/dist/features/i18n/template-i18n.js +12 -0
  55. package/dist/features/interactive-ui/interactive-ui.css +1 -1
  56. package/dist/features/interactive-ui/providers/recharts/bar-chart.js +3 -2
  57. package/dist/features/interactive-ui/providers/recharts/chart-tokens.d.ts +43 -0
  58. package/dist/features/interactive-ui/providers/recharts/chart-tokens.js +44 -0
  59. package/dist/features/interactive-ui/providers/recharts/line-chart.js +3 -2
  60. package/dist/features/interactive-ui/providers/recharts/pie-chart.js +2 -2
  61. package/dist/features/lexical-rich-text-editor/deserialize.d.ts +0 -5
  62. package/dist/features/lexical-rich-text-editor/deserialize.js +0 -5
  63. package/dist/features/lexical-rich-text-editor/mention-node.d.ts +5 -17
  64. package/dist/features/lexical-rich-text-editor/mention-node.js +5 -17
  65. package/dist/features/lexical-rich-text-editor/mention-parser.d.ts +2 -4
  66. package/dist/features/lexical-rich-text-editor/serialize.d.ts +0 -4
  67. package/dist/features/lexical-rich-text-editor/serialize.js +0 -4
  68. package/dist/features/lexical-rich-text-editor/types.d.ts +3 -13
  69. package/dist/features/lexical-rich-text-editor/types.js +3 -13
  70. package/dist/features/mcp-ui/early-message-buffer.d.ts +1 -2
  71. package/dist/features/mcp-ui/early-message-buffer.js +1 -2
  72. package/dist/features/mcp-ui/escape.d.ts +4 -0
  73. package/dist/features/mcp-ui/escape.js +2 -9
  74. package/dist/features/mcp-ui/index.d.ts +1 -0
  75. package/dist/features/mcp-ui/index.js +1 -0
  76. package/dist/features/mcp-ui/protocol.d.ts +2 -5
  77. package/dist/features/mcp-ui/protocol.js +2 -5
  78. package/dist/features/mcp-ui/resource.d.ts +47 -7
  79. package/dist/features/mcp-ui/resource.js +5 -8
  80. package/dist/features/mcp-ui/secret-card.d.ts +115 -0
  81. package/dist/features/mcp-ui/secret-card.js +137 -0
  82. package/dist/features/mcp-ui/surfaces/confirmation.js +5 -0
  83. package/dist/features/mcp-ui/surfaces/fields.d.ts +5 -2
  84. package/dist/features/mcp-ui/surfaces/fields.js +3 -1
  85. package/dist/features/mcp-ui/surfaces/form.d.ts +4 -1
  86. package/dist/features/mcp-ui/surfaces/form.js +65 -3
  87. package/dist/features/mcp-ui/surfaces/outcome.d.ts +2 -0
  88. package/dist/features/mcp-ui/surfaces/text-input.d.ts +14 -6
  89. package/dist/features/mcp-ui/surfaces/text-input.js +16 -2
  90. package/dist/features/mcp-ui/surfaces/tokens.js +1 -1
  91. package/dist/features/memory/react/components/MemoryConnectedPanel.d.ts +1 -1
  92. package/dist/features/memory/react/components/MemoryConnectedPanel.js +0 -1
  93. package/dist/features/panel-kit/helpers/format-timestamp.d.ts +5 -3
  94. package/dist/features/panel-kit/helpers/format-timestamp.js +13 -4
  95. package/dist/features/panel-kit/helpers/permissions.js +2 -0
  96. package/dist/features/panel-kit/helpers/resolve-active-tab-id.js +2 -0
  97. package/dist/features/panel-kit/helpers/standing-draft-local-backup.d.ts +51 -0
  98. package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +77 -0
  99. package/dist/features/panel-kit/helpers/standing-draft-recovery-message.d.ts +8 -0
  100. package/dist/features/panel-kit/helpers/standing-draft-recovery-message.js +10 -0
  101. package/dist/features/panel-kit/hooks/use-async-action.hooks.d.ts +1 -0
  102. package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.d.ts +1 -0
  103. package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.js +1 -0
  104. package/dist/features/panel-kit/hooks/use-serial-writes.hooks.d.ts +8 -0
  105. package/dist/features/panel-kit/hooks/use-serial-writes.hooks.js +3 -0
  106. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.d.ts +211 -0
  107. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +382 -0
  108. package/dist/features/panel-kit/hooks/use-standing-draft-autosave.hooks.d.ts +179 -0
  109. package/dist/features/panel-kit/hooks/use-standing-draft-autosave.hooks.js +300 -0
  110. package/dist/features/panel-kit/index.d.ts +7 -1
  111. package/dist/features/panel-kit/index.js +5 -1
  112. package/dist/features/resource-dashboard/types.d.ts +8 -41
  113. package/dist/features/resource-dashboard/types.js +3 -26
  114. package/dist/features/tab-strip/constants.d.ts +3 -4
  115. package/dist/features/tab-strip/constants.js +3 -4
  116. package/dist/features/tab-strip/dependencies.d.ts +1 -6
  117. package/dist/features/tab-strip/dependencies.js +1 -6
  118. package/dist/features/tab-strip/react/components/TabBar.d.ts +33 -1
  119. package/dist/features/tab-strip/react/components/TabBar.hooks.d.ts +57 -5
  120. package/dist/features/tab-strip/react/components/TabBar.hooks.js +63 -5
  121. package/dist/features/tab-strip/react/components/TabBar.js +12 -1
  122. package/dist/features/tab-strip/rules.d.ts +4 -17
  123. package/dist/features/tab-strip/rules.js +4 -17
  124. package/dist/features/tab-strip/types.d.ts +2 -13
  125. package/dist/icon-name.d.ts +1 -1
  126. package/dist/icon-name.js +1 -0
  127. package/dist/index.d.ts +42 -417
  128. package/dist/index.js +42 -430
  129. package/dist/react/components/CustomSelect.d.ts +6 -2
  130. package/dist/react/components/CustomSelect.js +20 -91
  131. package/dist/react/components/Icon.d.ts +6 -0
  132. package/dist/react/components/Icon.js +12 -0
  133. package/dist/react/components/select-popover.d.ts +106 -0
  134. package/dist/react/components/select-popover.js +210 -0
  135. package/dist/renderers/sandbox-scripts.d.ts +29 -0
  136. package/dist/renderers/sandbox-scripts.js +127 -0
  137. package/dist/renderers/sandboxed-document.js +3 -86
  138. package/dist/renderers/srcdoc/build.d.ts +10 -17
  139. package/dist/renderers/srcdoc/build.js +31 -234
  140. package/dist/styles/variables.css +30 -0
  141. package/dist/theme/apply.d.ts +1 -1
  142. package/dist/theme/apply.js +2 -2
  143. package/dist/theme/color-scheme.d.ts +1 -1
  144. package/dist/theme/default.d.ts +1 -3
  145. package/dist/theme/default.js +2 -10
  146. package/dist/theme/index.d.ts +3 -3
  147. package/dist/theme/index.js +2 -2
  148. package/dist/theme/stylesheet.d.ts +1 -7
  149. package/dist/theme/stylesheet.js +2 -13
  150. package/dist/theme/types.d.ts +1 -38
  151. package/dist/theme/types.js +2 -1
  152. package/dist/theme/validation.d.ts +1 -14
  153. package/dist/theme/validation.js +2 -54
  154. package/dist/utils/endpoint-policy.d.ts +21 -39
  155. package/dist/utils/endpoint-policy.js +19 -107
  156. package/dist/utils/file-system-errors.d.ts +1 -2
  157. package/dist/utils/file-system-errors.js +1 -2
  158. package/dist/utils/ime-composing.d.ts +0 -3
  159. package/dist/utils/ime-composing.js +0 -3
  160. package/dist/utils/platform.d.ts +1 -4
  161. package/dist/utils/platform.js +1 -4
  162. package/dist/utils/smooth-scroll-to-top.d.ts +1 -3
  163. package/dist/utils/smooth-scroll-to-top.js +3 -13
  164. package/dist/utils/timezone.js +1 -5
  165. package/dist/utils/uuid.d.ts +0 -3
  166. package/dist/utils/uuid.js +0 -3
  167. package/dist/utils/visual-stability.d.ts +1 -3
  168. package/dist/utils/visual-stability.js +1 -3
  169. package/package.json +74 -15
@@ -1,4 +1,4 @@
1
- import { defaultAdminTheme } from '../../theme/default.js';
1
+ import { defaultAdminTheme } from '../../core/theme/default.js';
2
2
  /** Plain-English defaults for the three segmented-control options — a host
3
3
  * wanting localized copy overrides via `AppearanceTabProps.themeLabels` (the
4
4
  * component wraps each through `useT()` regardless, so this is only the
@@ -1,9 +1,4 @@
1
- /**
2
- * Pure logic ported from OD's ConnectorsBrowser.tsx + the two small pure
3
- * helpers it imported from EntryView.tsx (isTrustedConnectorCallbackOrigin,
4
- * sortConnectorsForSearch/getConnectorSearchScore/sortConnectorsForDisplay).
5
- * No React, no transport, no DOM — see `packages/ui/archived provenance ledger`.
6
- */
1
+ /** Connector callback-origin validation and search/display sorting; no React, transport or DOM. */
7
2
  import type { Connector, ConnectorAuthorizationPendingState, ConnectorActionResult, ConnectorStatusMap, ConnectorPanelAlert, ConnectorStatus, ProviderTab } from './types.js';
8
3
  /** Restricts the catalog to a single provider tab's `match` predicate, defaulting to the first tab. */
9
4
  export declare function scopeConnectorsToProvider(connectors: Connector[], providerTabs: readonly ProviderTab[], selectedProviderId: string): Connector[];
@@ -1,9 +1,4 @@
1
- /**
2
- * Generic OAuth-integration-marketplace domain types. Ported from the origin
3
- * product's ConnectorsBrowser.tsx (see the vendored reference tree cited in
4
- * `packages/ui/archived provenance ledger`), stripped of Connector Provider/product-specific
5
- * wire-shape specifics — see that file for the full provenance note.
6
- */
1
+ /** Generic OAuth-integration marketplace types; hosts own provider-specific wire shapes. */
7
2
  export type ConnectorStatus = 'available' | 'connected' | 'error' | 'disabled';
8
3
  export interface ConnectorToolSafety {
9
4
  sideEffect: string;
@@ -1,8 +1,3 @@
1
- /**
2
- * Generic OAuth-integration-marketplace domain types. Ported from the origin
3
- * product's ConnectorsBrowser.tsx (see the vendored reference tree cited in
4
- * `packages/ui/archived provenance ledger`), stripped of Connector Provider/product-specific
5
- * wire-shape specifics — see that file for the full provenance note.
6
- */
1
+ /** Generic OAuth-integration marketplace types; hosts own provider-specific wire shapes. */
7
2
  export {};
8
3
  //# sourceMappingURL=types.js.map
@@ -0,0 +1,111 @@
1
+ import type { ByokConfig, DetectedAgent } from './types.js';
2
+ import type { ExecutionPort } from './ports.js';
3
+ /** Decoded HTTP operations; hosts retain auth, URLs and transport validation. */
4
+ export interface ExecutionProbeTransport {
5
+ detectExecutionAgents(required: Record<string, never>, optional?: Record<string, never>): Promise<{
6
+ data: readonly DetectedAgent[];
7
+ }>;
8
+ testExecutionConnection(required: Pick<ByokConfig, "protocol" | "baseUrl" | "apiKey" | "model"> & ExecutionProbeFlags, optional?: Record<string, never>): Promise<{
9
+ ok: boolean;
10
+ message?: string;
11
+ }>;
12
+ testExecutionAgent(required: {
13
+ agentId: string;
14
+ model?: string;
15
+ }, optional?: Record<string, never>): ReturnType<NonNullable<ExecutionPort["testAgent"]>>;
16
+ listExecutionModels(required: Pick<ByokConfig, "protocol" | "baseUrl" | "apiKey"> & ExecutionProbeFlags, optional?: Record<string, never>): Promise<{
17
+ ok: boolean;
18
+ models: readonly string[];
19
+ message?: string;
20
+ }>;
21
+ }
22
+ interface ExecutionProbeFlags {
23
+ useStoredCredential?: boolean;
24
+ useAdminStoredCredential?: boolean;
25
+ }
26
+ /**
27
+ * Unwraps `{ data }` off an `api.*` response, or throws a named error identifying which call
28
+ * produced the malformed body.
29
+ *
30
+ * `request()` (`lib/api.ts`) validates shape on the FAILURE path only — a non-2xx always becomes
31
+ * an `ApiError` — but a 2xx body is returned as `body as T` with no runtime check that it actually
32
+ * has the shape `T` claims. Every caller in this file that read `.data` straight off that promise
33
+ * used to let a malformed 200 (body present, `data` missing/`null`) fall through to whatever the
34
+ * FIRST thing touching the result happened to be: `loadExecutionConfig`'s own `rows.map(...)`
35
+ * threw `Cannot read properties of undefined (reading 'map')`, while `loadAdminExecutionCredential`
36
+ * had no local dereference at all, so it silently RESOLVED to `undefined` and the crash only
37
+ * surfaced later, at whichever caller first read a field off it (live case: `AssistantDock.hooks
38
+ * .tsx`'s `view.isSet`, `Cannot read properties of undefined (reading 'isSet')`). Both are the same
39
+ * underlying defect wearing different symptoms depending on what the caller happened to do next.
40
+ * Centralizing the check here means every one of those call sites fails at its own source, with a
41
+ * message naming the actual `api.*` method — not a raw property-access trace an on-call engineer
42
+ * has to work backward from.
43
+ *
44
+ * Every existing caller's own failure contract is unchanged by this: `loadExecutionConfig`/
45
+ * `loadAdminExecutionCredential` still get caught by `AssistantDock`'s `.catch()`-and-log-and-
46
+ * fall-back-to-defaults; `saveAdminExecutionCredential` still surfaces as a failed save; the local
47
+ * agent-detection port's methods still reject, per this file's own error-reporting contract (see
48
+ * `requestAgentDetection`'s doc). This function only makes the THROWN message legible — it never
49
+ * suppresses a malformed response into a silent default (an earlier draft's `?? []` would have hidden
50
+ * the same failure behind "shows up as empty settings, no explanation" instead of fixing it).
51
+ *
52
+ * @complexity O(1) — one shape check, no iteration.
53
+ */
54
+ export declare function requireExecutionResponseData<T>({ response, callSite }: {
55
+ response: {
56
+ data?: T | null;
57
+ } | null | undefined;
58
+ callSite: string;
59
+ }, _options?: Record<string, never>): T;
60
+ export interface CreateExecutionPortOptions {
61
+ /**
62
+ * When `true`, probes that receive no typed API key ask the server to use the workspace's STORED
63
+ * site credential instead.
64
+ *
65
+ * For the AI Assistant tab, whose key is encrypted server-side and write-only: the operator
66
+ * returns to a screen with a saved, working key and an EMPTY field, so "Test connection" and model
67
+ * discovery had nothing to send and sat permanently disabled next to a credential that works.
68
+ *
69
+ * Left `false` for Settings → Execution mode, and that default is the safety property, not an
70
+ * oversight: that screen probes the ADMIN's own credential (server-side since 2026-08-05, but
71
+ * still a DIFFERENT stored row from the site's), a different purpose from this flag entirely. If
72
+ * it opted in, an empty field there would silently probe — and report results for — the visitor
73
+ * key, crossing the boundary ADR-058 §5 makes structural. (The admin's OWN stored fallback for a
74
+ * BYOK *turn* is unconditional and happens entirely server-side in `assistant-byok.ts` — this
75
+ * flag only ever concerns the SITE credential these `test-connection`/`list-models` probes can
76
+ * optionally fall back to.)
77
+ */
78
+ useStoredCredential?: boolean;
79
+ /**
80
+ * When `true`, probes that receive no typed API key ask the server to use the CALLING ADMIN'S OWN
81
+ * stored execution credential — the other write-only key, and a different row from
82
+ * {@link CreateExecutionPortOptions.useStoredCredential}'s.
83
+ *
84
+ * Same symptom on the other screen. The admin's own key moved server-side on 2026-08-05, so
85
+ * `ExecutionTab`'s Model field asked the server to discover models with an empty key and got back
86
+ * "No API key — model discovery needs the key from this browser" — beside a stored key the server
87
+ * uses on every admin turn. The visible consequence was that the admin BYOK panel never reached
88
+ * `modelDiscovery.status === 'ok'`, so `ByokProviderForm` fell back to its free-text Model input
89
+ * while the visitor panel next to it showed a real live-model picker from the same component.
90
+ *
91
+ * The server scopes the row to the SESSION's principal, so this can only ever reach the caller's
92
+ * own credential. Deliberately a second flag rather than a widened `useStoredCredential`: see
93
+ * `server/inbound/admin-http/routes/assistant/stored-credential-probe.ts`'s header.
94
+ */
95
+ useAdminStoredCredential?: boolean;
96
+ }
97
+ /** Create one adapter per host transport. Keep it outside React mounts so detection is shared.
98
+ * Failed detections remain retryable; rescans always replace the cache with a fresh request.
99
+ * Transport errors reject; connection/CLI outcomes may resolve ok:false.
100
+ * @complexity O(1) local work per operation, excluding transport.
101
+ * @example const adapter = createExecutionHttpAdapter({ transport }, {});
102
+ * const port = adapter.createPort({}, { useAdminStoredCredential: true });
103
+ */
104
+ export declare function createExecutionHttpAdapter({ transport }: {
105
+ transport: ExecutionProbeTransport;
106
+ }, _options?: Record<string, never>): {
107
+ createPort: (_required?: Record<string, never>, options?: CreateExecutionPortOptions) => ExecutionPort;
108
+ resetDetectionCache: (_required?: Record<string, never>, _optional?: Record<string, never>) => void;
109
+ };
110
+ export {};
111
+ //# sourceMappingURL=http-adapter.d.ts.map
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Unwraps `{ data }` off an `api.*` response, or throws a named error identifying which call
3
+ * produced the malformed body.
4
+ *
5
+ * `request()` (`lib/api.ts`) validates shape on the FAILURE path only — a non-2xx always becomes
6
+ * an `ApiError` — but a 2xx body is returned as `body as T` with no runtime check that it actually
7
+ * has the shape `T` claims. Every caller in this file that read `.data` straight off that promise
8
+ * used to let a malformed 200 (body present, `data` missing/`null`) fall through to whatever the
9
+ * FIRST thing touching the result happened to be: `loadExecutionConfig`'s own `rows.map(...)`
10
+ * threw `Cannot read properties of undefined (reading 'map')`, while `loadAdminExecutionCredential`
11
+ * had no local dereference at all, so it silently RESOLVED to `undefined` and the crash only
12
+ * surfaced later, at whichever caller first read a field off it (live case: `AssistantDock.hooks
13
+ * .tsx`'s `view.isSet`, `Cannot read properties of undefined (reading 'isSet')`). Both are the same
14
+ * underlying defect wearing different symptoms depending on what the caller happened to do next.
15
+ * Centralizing the check here means every one of those call sites fails at its own source, with a
16
+ * message naming the actual `api.*` method — not a raw property-access trace an on-call engineer
17
+ * has to work backward from.
18
+ *
19
+ * Every existing caller's own failure contract is unchanged by this: `loadExecutionConfig`/
20
+ * `loadAdminExecutionCredential` still get caught by `AssistantDock`'s `.catch()`-and-log-and-
21
+ * fall-back-to-defaults; `saveAdminExecutionCredential` still surfaces as a failed save; the local
22
+ * agent-detection port's methods still reject, per this file's own error-reporting contract (see
23
+ * `requestAgentDetection`'s doc). This function only makes the THROWN message legible — it never
24
+ * suppresses a malformed response into a silent default (an earlier draft's `?? []` would have hidden
25
+ * the same failure behind "shows up as empty settings, no explanation" instead of fixing it).
26
+ *
27
+ * @complexity O(1) — one shape check, no iteration.
28
+ */
29
+ export function requireExecutionResponseData({ response, callSite }, _options = {}) {
30
+ if (response === null || response === undefined || response.data === undefined || response.data === null) {
31
+ throw new Error(`${callSite} response missing data`);
32
+ }
33
+ return response.data;
34
+ }
35
+ /** Create one adapter per host transport. Keep it outside React mounts so detection is shared.
36
+ * Failed detections remain retryable; rescans always replace the cache with a fresh request.
37
+ * Transport errors reject; connection/CLI outcomes may resolve ok:false.
38
+ * @complexity O(1) local work per operation, excluding transport.
39
+ * @example const adapter = createExecutionHttpAdapter({ transport }, {});
40
+ * const port = adapter.createPort({}, { useAdminStoredCredential: true });
41
+ */
42
+ export function createExecutionHttpAdapter({ transport }, _options = {}) {
43
+ /**
44
+ * host's real `ExecutionPort` — wired to `src/server/modules/assistant-
45
+ * execution.ts`'s 3 routes (`@jini-ai/agent-runtime` underneath). Follows
46
+ * `@jini-ai/ui`'s `ports.ts` contract (error-reporting contract §3.1):
47
+ *
48
+ * - `detectLocalAgents`/`rescanLocalAgents`/`listModels` REJECT on any
49
+ * failure (a network error, a non-2xx from this admin's own route, a
50
+ * provider that could not be reached). An empty array is reserved for a
51
+ * real "detection ran, found nothing" / "no models" outcome — collapsing a
52
+ * broken request into `[]` would make it indistinguishable from that,
53
+ * which is exactly the bug this port used to have.
54
+ * - `testConnection` is the one documented exception: `api.testExecutionConnection`
55
+ * already classifies "reached the provider, credentials rejected" as an
56
+ * `{ok:false}` VALUE server-side (`test-connection.ts`), so this method
57
+ * simply returns whatever it resolves with. It only REJECTS when the
58
+ * route call itself fails (session expired, network down, this admin's own
59
+ * route 500ing) — a case with no provider-side answer to report as a value.
60
+ */
61
+ async function requestAgentDetection() {
62
+ return requireExecutionResponseData({ response: await transport.detectExecutionAgents({}, {}), callSite: "detectExecutionAgents" }, {});
63
+ }
64
+ /**
65
+ * Last detection, shared across every `ExecutionTab` mount in this tab's
66
+ * lifetime.
67
+ *
68
+ * Detection is not cheap and it is not local: `detect-agents.ts` calls
69
+ * `@jini-ai/agent-runtime`'s `detectAgents()`, which SPAWNS every known CLI
70
+ * with `--version` — two dozen processes on the host server per call. The tab
71
+ * asks for it unconditionally on mount (`useExecutionTab.ts`'s auto-detect
72
+ * effect), and the shell renders only the ACTIVE tab's panel, so leaving
73
+ * Execution mode unmounts the component and returning to it re-runs the whole
74
+ * sweep. Same on every visit to the settings page. Without this cache the
75
+ * operator pays a fresh 24-process scan for what is, in practice, a fixed
76
+ * answer.
77
+ *
78
+ * Host-scoped rather than a `useRef`/context on purpose: the point is to
79
+ * outlive the component, and `SettingsUi` builds a NEW port object per mount
80
+ * (`useRef(createExecutionPort())`), so anything held on the port instance
81
+ * would die with it.
82
+ *
83
+ * Deliberately no TTL. Installing a CLI mid-session is the only way this goes
84
+ * stale, and that case already has a purpose-built, clearly-labelled escape
85
+ * hatch — the Rescan button, which bypasses this cache (see
86
+ * `rescanLocalAgents` below). A reload clears it too. A TTL would only add a
87
+ * second, invisible refresh rule on top of the explicit one.
88
+ */
89
+ let cachedDetection = null;
90
+ /** Caches the in-flight promise, not just the settled value — two tabs
91
+ * mounting in the same frame then share one request instead of racing two
92
+ * identical process sweeps. */
93
+ function cacheDetection(inFlight) {
94
+ cachedDetection = inFlight;
95
+ // A rejection must never be cached: one network blip would otherwise pin the
96
+ // error in place for the rest of the session, and the port's contract is
97
+ // that a failure REJECTS (see this function's doc) — so a retry has to be
98
+ // able to actually retry.
99
+ inFlight.catch(() => {
100
+ if (cachedDetection === inFlight)
101
+ cachedDetection = null;
102
+ });
103
+ return inFlight;
104
+ }
105
+ /** Test seam. Module state persists across cases in a file, so a suite that
106
+ * asserts on detection has to be able to start from cold. */
107
+ function resetLocalAgentDetectionCache(_required = {}, _optional = {}) {
108
+ cachedDetection = null;
109
+ }
110
+ function createExecutionPort(_required = {}, options = {}) {
111
+ const { useStoredCredential = false, useAdminStoredCredential = false } = options;
112
+ return {
113
+ async detectLocalAgents() {
114
+ return cachedDetection ?? cacheDetection(requestAgentDetection());
115
+ },
116
+ async rescanLocalAgents() {
117
+ // Explicit operator action: always re-probe, and make the fresh result
118
+ // the new baseline for subsequent mounts.
119
+ return cacheDetection(requestAgentDetection());
120
+ },
121
+ async testConnection(config) {
122
+ return transport.testExecutionConnection({
123
+ protocol: config.protocol,
124
+ baseUrl: config.baseUrl,
125
+ apiKey: config.apiKey,
126
+ model: config.model,
127
+ ...(useStoredCredential ? { useStoredCredential: true } : {}),
128
+ ...(useAdminStoredCredential ? { useAdminStoredCredential: true } : {}),
129
+ }, {});
130
+ },
131
+ async testAgent(agentId, model) {
132
+ // Same `{ok:false}`-is-a-value split as `testConnection`: the route
133
+ // classifies "the CLI ran and reported it is not usable" server-side and
134
+ // returns it as a value, and REJECTS (throws out of `api.*`) only when
135
+ // the probe could not run at all.
136
+ return transport.testExecutionAgent({ agentId, ...(model ? { model } : {}) }, {});
137
+ },
138
+ async listModels(config) {
139
+ const result = await transport.listExecutionModels({
140
+ protocol: config.protocol,
141
+ baseUrl: config.baseUrl,
142
+ apiKey: config.apiKey,
143
+ ...(useStoredCredential ? { useStoredCredential: true } : {}),
144
+ ...(useAdminStoredCredential ? { useAdminStoredCredential: true } : {}),
145
+ }, {});
146
+ // `result.ok === false` here is ALWAYS "discovery could not reach/read the
147
+ // provider" (auth failure, timeout, blocked base URL, unsupported protocol)
148
+ // — `list-models.ts`'s route never reports a reachable-but-genuinely-empty
149
+ // catalog as `ok:false`, so there is no legitimate value to return here.
150
+ // Reject with the server's own message rather than resolving `[]`.
151
+ if (!result.ok) {
152
+ throw new Error(result.message?.trim() ? result.message : "Model discovery failed");
153
+ }
154
+ return result.models;
155
+ },
156
+ };
157
+ }
158
+ return { createPort: createExecutionPort, resetDetectionCache: resetLocalAgentDetectionCache };
159
+ }
160
+ //# sourceMappingURL=http-adapter.js.map
@@ -21,4 +21,8 @@ export { SearchableModelSelect } from './react/components/SearchableModelSelect.
21
21
  export type { SearchableModelSelectProps, SearchableModelSelectAdditionalOption, } from './react/components/SearchableModelSelect.js';
22
22
  export { useExecutionTab } from './react/hooks/useExecutionTab.js';
23
23
  export type { UseExecutionTabOptions, UseExecutionTabResult } from './react/hooks/useExecutionTab.js';
24
+ export { createExecutionHttpAdapter, requireExecutionResponseData } from './http-adapter.js';
25
+ export type { CreateExecutionPortOptions, ExecutionProbeTransport } from './http-adapter.js';
26
+ export { useExecutionCredential } from './react/hooks/use-execution-credential.hooks.js';
27
+ export type { UseExecutionCredentialInput, ExecutionCredentialController, ExecutionCredentialSaveState, ExecutionCredentialView, ExecutionCredentialPatch, ExecutionCredentialPort, ExecutionCredentialEffects } from './react/hooks/use-execution-credential.hooks.js';
24
28
  //# sourceMappingURL=index.d.ts.map
@@ -10,4 +10,6 @@ export { AgentDiagnosticRow } from './react/components/AgentDiagnosticRow.js';
10
10
  export { AgentCliEnvFields } from './react/components/AgentCliEnvFields.js';
11
11
  export { SearchableModelSelect } from './react/components/SearchableModelSelect.js';
12
12
  export { useExecutionTab } from './react/hooks/useExecutionTab.js';
13
+ export { createExecutionHttpAdapter, requireExecutionResponseData } from './http-adapter.js';
14
+ export { useExecutionCredential } from './react/hooks/use-execution-credential.hooks.js';
13
15
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,232 @@
1
+ import type { ByokConfig } from "../../types.js";
2
+ import type { SettingsSliceRefreshPort } from "../../../panel-kit/hooks/use-settings-slice.hooks.js";
3
+ /** The metadata read by this controller. Hosts retain any additional write-only view fields. */
4
+ export interface ExecutionCredentialView {
5
+ isSet: boolean;
6
+ masked: string | null;
7
+ baseUrl: string | null;
8
+ }
9
+ /** Explicit key and non-secret patches remain distinct; the controller never autosaves a key. */
10
+ export interface ExecutionCredentialPatch {
11
+ apiKey?: string;
12
+ protocol?: string;
13
+ providerId?: string | null;
14
+ baseUrl?: string;
15
+ model?: string;
16
+ maxTokens?: number;
17
+ }
18
+ /** The host owns routes and authorization; only a write-only view may be returned. */
19
+ export interface ExecutionCredentialPort<TStored extends ExecutionCredentialView> {
20
+ loadAdminExecutionCredential(required: Record<string, never>, optional?: Record<string, never>): Promise<TStored>;
21
+ saveAdminExecutionCredential(required: {
22
+ patch: ExecutionCredentialPatch;
23
+ }, optional?: Record<string, never>): Promise<TStored>;
24
+ }
25
+ /** Host migration, notification and display policies. Product-specific error/copy stays in the host. */
26
+ export interface ExecutionCredentialEffects<TStored extends ExecutionCredentialView> {
27
+ namespace: string;
28
+ refreshPort: SettingsSliceRefreshPort;
29
+ readLegacyCredential(required: Record<string, never>, optional?: Record<string, never>): string | null;
30
+ clearLegacyCredential(required: Record<string, never>, optional?: Record<string, never>): void;
31
+ describeError(required: {
32
+ error: unknown;
33
+ fallback: string;
34
+ }, optional?: Record<string, never>): string;
35
+ hasTypedKey(required: {
36
+ apiKey: string;
37
+ }, optional?: Record<string, never>): boolean;
38
+ storedKeyIsForOtherEndpoint(required: {
39
+ stored: TStored | null;
40
+ baseUrl: string;
41
+ }, optional?: Record<string, never>): boolean;
42
+ hasUsableKey(required: {
43
+ apiKey: string;
44
+ stored: TStored | null;
45
+ baseUrl: string;
46
+ }, optional?: Record<string, never>): boolean;
47
+ storedKeyBlocksProbe(required: {
48
+ apiKey: string;
49
+ stored: TStored | null;
50
+ baseUrl: string;
51
+ }, optional?: Record<string, never>): boolean;
52
+ credentialHint(required: {
53
+ stored: TStored | null;
54
+ storedKeyIsForOtherEndpoint: boolean;
55
+ }, optional?: {
56
+ locale?: string | undefined;
57
+ }): string | undefined;
58
+ }
59
+ /**
60
+ * @file State for the admin's own BYOK credential — the "Save key" and "Save settings" controls and
61
+ * the one-time `localStorage`-to-server migration prompt, shared verbatim by `SettingsUi.tsx`'s Execution tab
62
+ * and `AiAssistant.tsx`'s `AdminExecutionMode` (both render `ExecutionTab` over the SAME ledger
63
+ * namespace and now the same server-side credential row; per `AdminExecutionMode`'s own "must never
64
+ * disagree" comment about `useStoredCredential`, the two mounts cannot be allowed to diverge here
65
+ * either — hence one hook rather than two independent implementations).
66
+ *
67
+ * Modeled closely on the already-shipped, owner-reviewed
68
+ * `features/ai-assistant/hooks/use-visitor-credential-form.hooks.ts`: explicit save only, the field
69
+ * clears on success, a failed save never touches what is already stored/local. The two differ where
70
+ * the underlying screens differ — this one does not own the whole BYOK form (only the "Save key"
71
+ * affordance under the key field and the "Save settings" affordance at the foot of the card, since
72
+ * `ExecutionTab` owns the provider chips and the fields themselves via the existing `core.execution`
73
+ * ledger slice) and it additionally owns the migration prompt, which the visitor screen has no
74
+ * equivalent of (there was never a browser-local visitor key to migrate).
75
+ *
76
+ * ## One button per patch (owner ruling, 2026-09-02)
77
+ *
78
+ * There used to be ONE control here, writing the key and the row's non-secret companion fields
79
+ * together. That overload is what made the screen lie: pressing it with a blank field sent a patch
80
+ * with no `apiKey` at all and still answered "Saved to the server, encrypted." Patching the message
81
+ * left the cause in place, and disabling the button on a blank field broke the only way to save a
82
+ * model change. The split closes both: `saveKey` writes `{ apiKey }`, `saveSettings` writes the
83
+ * non-secret fields and never an `apiKey` property, and each carries its own save state so neither
84
+ * can report the other's work.
85
+ *
86
+ * ## Cross-mount staleness (disclosed residual, closed here)
87
+ *
88
+ * `stored`/`apiKeyStoredExternally` is each mounted instance's OWN copy of one server fact — every
89
+ * `SettingsUi.tsx` mount, every `AiAssistant.tsx` mount, AND `AssistantDock.hooks.tsx`'s own
90
+ * independent `loadAdminExecutionCredential()` call (`useExecutionConfig`'s `hasStoredAdminKey`) are
91
+ * three separate reads of the same row. A save/migrate in ONE of them updated only its own local
92
+ * `stored` state, so the other two kept showing whatever they last fetched — briefly, if the operator
93
+ * happened to remount that screen soon after, or indefinitely for the dock, which mounts once at the
94
+ * app shell and never remounts for the session (`AssistantDock.tsx`'s own header comment). Now wired
95
+ * to `settings-refresh-bus.ts`, the same seam `useSettingsSlice`'s `runSave` already uses for exactly
96
+ * this "same-tab sibling with no shared state" problem: every successful write publishes
97
+ * `EXECUTION_NAMESPACE`, and every mount (including the publisher's own, harmlessly — see
98
+ * `runSave`'s doc for why that redundant self-refresh is accepted rather than tracked around) re-reads
99
+ * on receiving it.
100
+ *
101
+ * `port` (the two functions that reach `api.getAdminExecutionCredential`/`api.setAdminExecutionCredential`
102
+ * under the hood) is injected — see `admin-execution-credential-port.hooks.ts` for exactly what is and
103
+ * is not in it, and why. `useWiredAdminExecutionCredential` below is the zero-argument pair
104
+ * `SettingsUi.tsx`/`AiAssistant.tsx` actually mount; `AssistantDock.hooks.tsx`'s own separate
105
+ * `loadAdminExecutionCredential()` call (this file's "Cross-mount staleness" doc above) is a
106
+ * different hook entirely, out of scope here.
107
+ */
108
+ export type ExecutionCredentialSaveState = {
109
+ status: "idle";
110
+ } | {
111
+ status: "saving";
112
+ } | {
113
+ status: "saved";
114
+ } | {
115
+ status: "error";
116
+ message: string;
117
+ };
118
+ export interface UseExecutionCredentialInput {
119
+ locale?: string | undefined;
120
+ /** The live BYOK half of whichever `ExecutionConfig` the host owns. Read fresh on every
121
+ * `saveKey()`/`migrateLegacyKey()` call (never captured once), so a save always sends whatever
122
+ * protocol/baseUrl/model/maxTokens the operator currently has selected. */
123
+ byok: ByokConfig;
124
+ /** How a successful key save clears the just-saved plaintext back out of the host's own state —
125
+ * same reasoning `use-visitor-credential-form.hooks.ts`'s `saveCredential` documents: leaving it
126
+ * in React state after it is safely persisted keeps it readable in devtools for no benefit, and
127
+ * the masked placeholder now carries the "which key" answer the field would otherwise be giving. */
128
+ onByokChange: (next: ByokConfig) => void;
129
+ }
130
+ export interface ExecutionCredentialController<TStored extends ExecutionCredentialView> {
131
+ /** The server's write-only view of what is currently stored — `null` until the initial GET
132
+ * settles. Never contains key material. */
133
+ stored: TStored | null;
134
+ /** Tells `ExecutionTab`'s `apiKeyStoredExternally` prop that an empty key field is not a missing
135
+ * required field, because a credential already exists server-side FOR THE FORM'S ENDPOINT. A key
136
+ * stored for another endpoint does not count (the server will not send it here), so after a
137
+ * provider switch the field is required again and Test connection waits for a typed key. Same rule
138
+ * as the visitor screen: `lib/stored-credential-endpoint.ts`'s `hasUsableKey`. */
139
+ apiKeyStoredExternally: boolean;
140
+ /** The server's `••••<last 4>`, or `undefined` when nothing is stored or the stored key belongs to
141
+ * another endpoint (under a different provider it would read as that provider's key) — feeds
142
+ * `ExecutionTab`'s `apiKeyPlaceholder`. Always a placeholder, never a value: `byok.apiKey` is
143
+ * untouched by this controller until an explicit save succeeds. */
144
+ apiKeyPlaceholder: string | undefined;
145
+ /** A key is stored, but the server saved it for another endpoint than the form's (a provider
146
+ * switch). Drives the key footer's "paste a key for this one" line. See
147
+ * `lib/stored-credential-endpoint.ts`'s `storedKeyIsForOtherEndpoint`; `false` until the stored
148
+ * view loads. */
149
+ storedKeyIsForOtherEndpoint: boolean;
150
+ /** Feeds `ExecutionTab`'s `canDiscoverModels`: `false` only when nothing is typed and the stored key
151
+ * belongs to another endpoint, the one discovery the server can only refuse. `ExecutionTab` reads
152
+ * it but never probes merely because it turned `true` — "Save settings" re-pointing the stored row
153
+ * flips it, and that must not send the old provider's key to the new endpoint unasked. */
154
+ canDiscoverModels: boolean;
155
+ /** The "Save key" button's own save state — and the migration prompt's, which writes the same
156
+ * key. Never advanced by {@link saveSettings}; see {@link settingsSaveState}. */
157
+ saveState: ExecutionCredentialSaveState;
158
+ /** The "Save settings" button's own save state, deliberately separate from {@link saveState}.
159
+ *
160
+ * One shared state is what made the old single control dishonest: its status line said "Saved to
161
+ * the server, encrypted." after a press that sent no key at all. Two buttons that write two
162
+ * disjoint patches need two answers, so each owns its own status line and neither can report the
163
+ * other's work. */
164
+ settingsSaveState: ExecutionCredentialSaveState;
165
+ /** Whether {@link saveKey} would do anything right now — the same "is there a key in the field"
166
+ * guard `saveKey` itself applies, exposed so the Save key button can disable correctly without
167
+ * duplicating the rule.
168
+ *
169
+ * Derived synchronously from the trimmed field on every render, never debounced: a delayed
170
+ * evaluation would leave the button enabled for the moment right after the field is cleared,
171
+ * which is the bug this rule exists to close, merely shortened. */
172
+ canSaveKey: boolean;
173
+ /**
174
+ * Explicit save, called ONLY from a "Save key" button press — never from any debounced or
175
+ * automatic path. Writes the KEY and nothing else: the patch is `{ apiKey }`, with no
176
+ * protocol/providerId/baseUrl/model/maxTokens riding along.
177
+ *
178
+ * That narrowing (owner ruling, 2026-09-02) is the fix for a whole defect class rather than one
179
+ * bug. A control that wrote two different things could not honestly report what it had just done,
180
+ * and the "which of my two jobs did I do" bookkeeping that answer needed was itself the thing that
181
+ * let an `apiKey: ""` reach the server. A button with one job needs no bookkeeping. The
182
+ * non-secret fields moved to {@link saveSettings}.
183
+ *
184
+ * A no-op when the field is empty — see {@link canSaveKey}.
185
+ */
186
+ saveKey: () => Promise<void>;
187
+ /**
188
+ * Explicit save, called ONLY from a "Save settings" button press. Writes
189
+ * protocol/providerId/baseUrl/model/maxTokens and NEVER an `apiKey` property — not even an empty
190
+ * string, which the server rejects (400) and which is precisely the write the old overloaded
191
+ * control could make.
192
+ *
193
+ * ## Why this is not a second writer for the ledger's values
194
+ *
195
+ * The same protocol/baseUrl/model the operator edits here ALSO live in the `core.execution`
196
+ * settings ledger, written on their own debounced path by `use-admin-execution-mode.hooks.ts`'s
197
+ * `useSettingsSlice`. Two stores, deliberately (see `execution-settings.ts`'s
198
+ * `loadAdminExecutionCredential` doc): the ledger holds the live values this browser's form shows,
199
+ * while the credential ROW holds a snapshot the SERVER reads when a browser sends no credential of
200
+ * its own — `byok-credential.ts`'s `createStoredExecutionCredentialPort`, which treats a row with
201
+ * no `model` as unusable.
202
+ *
203
+ * Before the split, that snapshot was refreshed as a side effect of every key save. With
204
+ * {@link saveKey} narrowed to the key, this button is the ONLY thing that writes it. Each of the
205
+ * two stores still has exactly one writer; what changed is that the credential row's writer is now
206
+ * a control the operator can see and press, rather than a hidden passenger on a different button.
207
+ */
208
+ saveSettings: () => Promise<void>;
209
+ /** A pre-server-store `localStorage` key this browser still holds, or `null` when there is
210
+ * nothing to migrate (never had one, already migrated, or the prompt was dismissed this
211
+ * session). Non-`null` is what a caller renders the migration banner on. */
212
+ legacyKey: string | null;
213
+ /** Confirms the migration: PUTs the legacy key (plus the current `byok` non-secret fields) to the
214
+ * server, and clears the `localStorage` entry ONLY once that PUT has actually succeeded — a
215
+ * failed save leaves the local copy fully intact, per the design's explicit "no auto-clear"
216
+ * requirement. */
217
+ migrateLegacyKey: () => Promise<void>;
218
+ /** Declines the prompt for the rest of this mount — no `localStorage` change either way. The
219
+ * design's "no auto-clear" rule means a decline must not delete the local key, so the prompt is
220
+ * free to reappear on a later mount (a page reload, navigating back to this tab); that is a minor
221
+ * repeat-ask, not a data-loss risk, which is the asymmetry the design accepts. */
222
+ dismissLegacyPrompt: () => void;
223
+ }
224
+ /**
225
+ * @complexity Time: O(1) per call — one GET on mount, one PUT per explicit save/migrate. Space:
226
+ * O(1) — no caller-controlled collections.
227
+ */
228
+ export declare function useExecutionCredential<TStored extends ExecutionCredentialView>({ byok, onByokChange, locale, port, effects }: UseExecutionCredentialInput & {
229
+ port: ExecutionCredentialPort<TStored>;
230
+ effects: ExecutionCredentialEffects<TStored>;
231
+ }, _options?: Record<string, never>): ExecutionCredentialController<TStored>;
232
+ //# sourceMappingURL=use-execution-credential.hooks.d.ts.map