@tai42/studio-sdk 25.0.1 → 27.0.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.
Files changed (262) hide show
  1. package/README.md +143 -26
  2. package/dist/components/action-menu.d.ts +30 -0
  3. package/dist/components/action-menu.d.ts.map +1 -0
  4. package/dist/components/action-menu.js +101 -0
  5. package/dist/components/action-menu.js.map +1 -0
  6. package/dist/components/agent-picker.d.ts +22 -0
  7. package/dist/components/agent-picker.d.ts.map +1 -0
  8. package/dist/components/agent-picker.js +17 -0
  9. package/dist/components/agent-picker.js.map +1 -0
  10. package/dist/components/channel-picker.d.ts +27 -0
  11. package/dist/components/channel-picker.d.ts.map +1 -0
  12. package/dist/components/channel-picker.js +26 -0
  13. package/dist/components/channel-picker.js.map +1 -0
  14. package/dist/components/components.css +226 -8
  15. package/dist/components/confirm-dialog.d.ts +3 -1
  16. package/dist/components/confirm-dialog.d.ts.map +1 -1
  17. package/dist/components/confirm-dialog.js +2 -2
  18. package/dist/components/confirm-dialog.js.map +1 -1
  19. package/dist/components/execution-key-picker.d.ts +26 -10
  20. package/dist/components/execution-key-picker.d.ts.map +1 -1
  21. package/dist/components/execution-key-picker.js +134 -15
  22. package/dist/components/execution-key-picker.js.map +1 -1
  23. package/dist/components/extension-combo-builder.d.ts +7 -7
  24. package/dist/components/extension-combo-builder.d.ts.map +1 -1
  25. package/dist/components/extension-combo-builder.js +42 -16
  26. package/dist/components/extension-combo-builder.js.map +1 -1
  27. package/dist/components/extension-combo-parts.d.ts +13 -11
  28. package/dist/components/extension-combo-parts.d.ts.map +1 -1
  29. package/dist/components/extension-combo-parts.js +10 -19
  30. package/dist/components/extension-combo-parts.js.map +1 -1
  31. package/dist/components/extension-config-editor.d.ts +33 -0
  32. package/dist/components/extension-config-editor.d.ts.map +1 -0
  33. package/dist/components/extension-config-editor.js +190 -0
  34. package/dist/components/extension-config-editor.js.map +1 -0
  35. package/dist/components/extension-grouping.d.ts +4 -41
  36. package/dist/components/extension-grouping.d.ts.map +1 -1
  37. package/dist/components/extension-grouping.js +1 -48
  38. package/dist/components/extension-grouping.js.map +1 -1
  39. package/dist/components/extension-picker.d.ts +4 -3
  40. package/dist/components/extension-picker.d.ts.map +1 -1
  41. package/dist/components/extension-picker.js +6 -6
  42. package/dist/components/extension-picker.js.map +1 -1
  43. package/dist/components/fleet-report.d.ts +8 -3
  44. package/dist/components/fleet-report.d.ts.map +1 -1
  45. package/dist/components/fleet-report.js +12 -2
  46. package/dist/components/fleet-report.js.map +1 -1
  47. package/dist/components/holder-kinds.d.ts +18 -0
  48. package/dist/components/holder-kinds.d.ts.map +1 -0
  49. package/dist/components/holder-kinds.js +41 -0
  50. package/dist/components/holder-kinds.js.map +1 -0
  51. package/dist/components/holder-list.d.ts +58 -0
  52. package/dist/components/holder-list.d.ts.map +1 -0
  53. package/dist/components/holder-list.js +77 -0
  54. package/dist/components/holder-list.js.map +1 -0
  55. package/dist/components/icons/index.d.ts +1 -1
  56. package/dist/components/icons/index.d.ts.map +1 -1
  57. package/dist/components/icons/index.js +1 -1
  58. package/dist/components/icons/index.js.map +1 -1
  59. package/dist/components/icons/nav-map.d.ts.map +1 -1
  60. package/dist/components/icons/nav-map.js +1 -3
  61. package/dist/components/icons/nav-map.js.map +1 -1
  62. package/dist/components/icons/nav-marks.d.ts +0 -4
  63. package/dist/components/icons/nav-marks.d.ts.map +1 -1
  64. package/dist/components/icons/nav-marks.js +0 -4
  65. package/dist/components/icons/nav-marks.js.map +1 -1
  66. package/dist/components/named-list-picker.d.ts +39 -0
  67. package/dist/components/named-list-picker.d.ts.map +1 -0
  68. package/dist/components/named-list-picker.js +61 -0
  69. package/dist/components/named-list-picker.js.map +1 -0
  70. package/dist/components/named-list-test-utils.d.ts +23 -0
  71. package/dist/components/named-list-test-utils.d.ts.map +1 -0
  72. package/dist/components/named-list-test-utils.js +33 -0
  73. package/dist/components/named-list-test-utils.js.map +1 -0
  74. package/dist/components/recycle-roll.d.ts +23 -0
  75. package/dist/components/recycle-roll.d.ts.map +1 -0
  76. package/dist/components/recycle-roll.js +47 -0
  77. package/dist/components/recycle-roll.js.map +1 -0
  78. package/dist/components/stepper.d.ts +28 -0
  79. package/dist/components/stepper.d.ts.map +1 -0
  80. package/dist/components/stepper.js +33 -0
  81. package/dist/components/stepper.js.map +1 -0
  82. package/dist/components/subject-section.d.ts +2 -1
  83. package/dist/components/subject-section.d.ts.map +1 -1
  84. package/dist/components/subject-section.js +10 -5
  85. package/dist/components/subject-section.js.map +1 -1
  86. package/dist/components/tokens.css +31 -41
  87. package/dist/components/tokens.d.ts +3 -4
  88. package/dist/components/tokens.d.ts.map +1 -1
  89. package/dist/components/tokens.js +5 -13
  90. package/dist/components/tokens.js.map +1 -1
  91. package/dist/components/tool-picker-parts.d.ts +5 -1
  92. package/dist/components/tool-picker-parts.d.ts.map +1 -1
  93. package/dist/components/tool-picker-parts.js +8 -6
  94. package/dist/components/tool-picker-parts.js.map +1 -1
  95. package/dist/components/tool-picker.d.ts +77 -2
  96. package/dist/components/tool-picker.d.ts.map +1 -1
  97. package/dist/components/tool-picker.js +87 -29
  98. package/dist/components/tool-picker.js.map +1 -1
  99. package/dist/components/version-history-panel.d.ts +7 -1
  100. package/dist/components/version-history-panel.d.ts.map +1 -1
  101. package/dist/components/version-history-panel.js +2 -2
  102. package/dist/components/version-history-panel.js.map +1 -1
  103. package/dist/components/version-history-rows.d.ts +4 -2
  104. package/dist/components/version-history-rows.d.ts.map +1 -1
  105. package/dist/components/version-history-rows.js +14 -2
  106. package/dist/components/version-history-rows.js.map +1 -1
  107. package/dist/errors.d.ts +5 -0
  108. package/dist/errors.d.ts.map +1 -1
  109. package/dist/errors.js +7 -0
  110. package/dist/errors.js.map +1 -1
  111. package/dist/hooks/useApi.d.ts +524 -197
  112. package/dist/hooks/useApi.d.ts.map +1 -1
  113. package/dist/hooks/useCapabilities.d.ts +43 -10
  114. package/dist/hooks/useCapabilities.d.ts.map +1 -1
  115. package/dist/hooks/useCapabilities.js +39 -10
  116. package/dist/hooks/useCapabilities.js.map +1 -1
  117. package/dist/index.d.ts +25 -12
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +14 -6
  120. package/dist/index.js.map +1 -1
  121. package/dist/marketplace-install/env-var-fields.d.ts +17 -0
  122. package/dist/marketplace-install/env-var-fields.d.ts.map +1 -0
  123. package/dist/marketplace-install/env-var-fields.js +22 -0
  124. package/dist/marketplace-install/env-var-fields.js.map +1 -0
  125. package/dist/marketplace-install/index.d.ts +18 -0
  126. package/dist/marketplace-install/index.d.ts.map +1 -0
  127. package/dist/marketplace-install/index.js +8 -0
  128. package/dist/marketplace-install/index.js.map +1 -0
  129. package/dist/marketplace-install/install-env.d.ts +21 -0
  130. package/dist/marketplace-install/install-env.d.ts.map +1 -0
  131. package/dist/marketplace-install/install-env.js +22 -0
  132. package/dist/marketplace-install/install-env.js.map +1 -0
  133. package/dist/marketplace-install/install-mount.d.ts +42 -0
  134. package/dist/marketplace-install/install-mount.d.ts.map +1 -0
  135. package/dist/marketplace-install/install-mount.js +62 -0
  136. package/dist/marketplace-install/install-mount.js.map +1 -0
  137. package/dist/marketplace-install/install-report.d.ts +14 -0
  138. package/dist/marketplace-install/install-report.d.ts.map +1 -0
  139. package/dist/marketplace-install/install-report.js +19 -0
  140. package/dist/marketplace-install/install-report.js.map +1 -0
  141. package/dist/marketplace-install/install-shell.d.ts +34 -0
  142. package/dist/marketplace-install/install-shell.d.ts.map +1 -0
  143. package/dist/marketplace-install/install-shell.js +32 -0
  144. package/dist/marketplace-install/install-shell.js.map +1 -0
  145. package/dist/marketplace-install/mount-install.d.ts +41 -0
  146. package/dist/marketplace-install/mount-install.d.ts.map +1 -0
  147. package/dist/marketplace-install/mount-install.js +90 -0
  148. package/dist/marketplace-install/mount-install.js.map +1 -0
  149. package/dist/marketplace-install/plugin-detail.d.ts +17 -0
  150. package/dist/marketplace-install/plugin-detail.d.ts.map +1 -0
  151. package/dist/marketplace-install/plugin-detail.js +41 -0
  152. package/dist/marketplace-install/plugin-detail.js.map +1 -0
  153. package/dist/marketplace-install/plugin-install.d.ts +27 -0
  154. package/dist/marketplace-install/plugin-install.d.ts.map +1 -0
  155. package/dist/marketplace-install/plugin-install.js +89 -0
  156. package/dist/marketplace-install/plugin-install.js.map +1 -0
  157. package/dist/marketplace-install/route-items.d.ts +14 -0
  158. package/dist/marketplace-install/route-items.d.ts.map +1 -0
  159. package/dist/marketplace-install/route-items.js +11 -0
  160. package/dist/marketplace-install/route-items.js.map +1 -0
  161. package/dist/marketplace-install/use-debounced-value.d.ts +6 -0
  162. package/dist/marketplace-install/use-debounced-value.d.ts.map +1 -0
  163. package/dist/marketplace-install/use-debounced-value.js +18 -0
  164. package/dist/marketplace-install/use-debounced-value.js.map +1 -0
  165. package/dist/marketplace-install/use-install-action.d.ts +26 -0
  166. package/dist/marketplace-install/use-install-action.d.ts.map +1 -0
  167. package/dist/marketplace-install/use-install-action.js +35 -0
  168. package/dist/marketplace-install/use-install-action.js.map +1 -0
  169. package/dist/marketplace-install/use-install-preview.d.ts +33 -0
  170. package/dist/marketplace-install/use-install-preview.d.ts.map +1 -0
  171. package/dist/marketplace-install/use-install-preview.js +44 -0
  172. package/dist/marketplace-install/use-install-preview.js.map +1 -0
  173. package/dist/marketplace-install/use-mount-install.d.ts +80 -0
  174. package/dist/marketplace-install/use-mount-install.d.ts.map +1 -0
  175. package/dist/marketplace-install/use-mount-install.js +75 -0
  176. package/dist/marketplace-install/use-mount-install.js.map +1 -0
  177. package/dist/navigation/index.d.ts +1 -1
  178. package/dist/navigation/index.d.ts.map +1 -1
  179. package/dist/navigation/index.js.map +1 -1
  180. package/dist/navigation/types.d.ts +23 -7
  181. package/dist/navigation/types.d.ts.map +1 -1
  182. package/dist/plugin/registry.d.ts +0 -8
  183. package/dist/plugin/registry.d.ts.map +1 -1
  184. package/dist/plugin/registry.js +25 -1
  185. package/dist/plugin/registry.js.map +1 -1
  186. package/dist/plugin/types.d.ts +36 -12
  187. package/dist/plugin/types.d.ts.map +1 -1
  188. package/dist/plugin/version.d.ts +6 -4
  189. package/dist/plugin/version.d.ts.map +1 -1
  190. package/dist/plugin/version.js +6 -4
  191. package/dist/plugin/version.js.map +1 -1
  192. package/dist/query-keys.d.ts +45 -4
  193. package/dist/query-keys.d.ts.map +1 -1
  194. package/dist/query-keys.js +52 -4
  195. package/dist/query-keys.js.map +1 -1
  196. package/dist/schema-form/json-schema-meta.d.ts +3 -0
  197. package/dist/schema-form/json-schema-meta.d.ts.map +1 -0
  198. package/dist/schema-form/json-schema-meta.js +3 -0
  199. package/dist/schema-form/json-schema-meta.js.map +1 -0
  200. package/dist/testing.d.ts +2 -0
  201. package/dist/testing.d.ts.map +1 -1
  202. package/dist/testing.js +2 -0
  203. package/dist/testing.js.map +1 -1
  204. package/dist/token-rules.d.ts +26 -0
  205. package/dist/token-rules.d.ts.map +1 -0
  206. package/dist/token-rules.js +64 -0
  207. package/dist/token-rules.js.map +1 -0
  208. package/package.json +2 -2
  209. package/src/components/action-menu.tsx +160 -0
  210. package/src/components/agent-picker.tsx +53 -0
  211. package/src/components/channel-picker.tsx +68 -0
  212. package/src/components/components.css +226 -8
  213. package/src/components/confirm-dialog.tsx +4 -1
  214. package/src/components/execution-key-picker.tsx +252 -36
  215. package/src/components/extension-combo-builder.tsx +61 -29
  216. package/src/components/extension-combo-parts.tsx +28 -38
  217. package/src/components/extension-config-editor.tsx +307 -0
  218. package/src/components/extension-grouping.ts +4 -73
  219. package/src/components/extension-picker.tsx +13 -13
  220. package/src/components/fleet-report.tsx +38 -4
  221. package/src/components/holder-kinds.ts +60 -0
  222. package/src/components/holder-list.tsx +163 -0
  223. package/src/components/icons/index.ts +0 -2
  224. package/src/components/icons/nav-map.ts +0 -4
  225. package/src/components/icons/nav-marks.tsx +0 -15
  226. package/src/components/named-list-picker.tsx +182 -0
  227. package/src/components/named-list-test-utils.tsx +47 -0
  228. package/src/components/recycle-roll.tsx +120 -0
  229. package/src/components/stepper.tsx +111 -0
  230. package/src/components/subject-section.tsx +10 -5
  231. package/src/components/tokens.css +31 -41
  232. package/src/components/tokens.ts +5 -13
  233. package/src/components/tool-picker-parts.tsx +17 -6
  234. package/src/components/tool-picker.tsx +215 -41
  235. package/src/components/version-history-panel.tsx +8 -0
  236. package/src/components/version-history-rows.tsx +14 -2
  237. package/src/errors.ts +10 -0
  238. package/src/hooks/useCapabilities.ts +59 -10
  239. package/src/index.ts +54 -14
  240. package/src/marketplace-install/env-var-fields.tsx +59 -0
  241. package/src/marketplace-install/index.ts +17 -0
  242. package/src/marketplace-install/install-env.ts +39 -0
  243. package/src/marketplace-install/install-mount.ts +96 -0
  244. package/src/marketplace-install/install-report.tsx +54 -0
  245. package/src/marketplace-install/install-shell.tsx +99 -0
  246. package/src/marketplace-install/mount-install.tsx +290 -0
  247. package/src/marketplace-install/plugin-detail.ts +60 -0
  248. package/src/marketplace-install/plugin-install.tsx +221 -0
  249. package/src/marketplace-install/route-items.ts +20 -0
  250. package/src/marketplace-install/use-debounced-value.ts +18 -0
  251. package/src/marketplace-install/use-install-action.ts +60 -0
  252. package/src/marketplace-install/use-install-preview.ts +74 -0
  253. package/src/marketplace-install/use-mount-install.ts +116 -0
  254. package/src/navigation/index.ts +1 -0
  255. package/src/navigation/types.ts +27 -7
  256. package/src/plugin/registry.ts +19 -1
  257. package/src/plugin/types.ts +40 -12
  258. package/src/plugin/version.ts +6 -4
  259. package/src/query-keys.ts +71 -4
  260. package/src/schema-form/json-schema-meta.ts +2 -0
  261. package/src/testing.ts +2 -0
  262. package/src/token-rules.ts +66 -0
@@ -121,7 +121,7 @@ export function useCapabilities(): CapabilityContextValue {
121
121
  * seeded editor/viewer carries `["*"]` scopes PLUS a jq fence, so keying on `"*"`
122
122
  * scopes would hand every editor/viewer the unfiltered nav and a wall of 403s on
123
123
  * the admin routes their fence denies. Keying on `admin` makes editor/viewer
124
- * SCOPED sessions whose nav is filtered by their jq-exact `routes`, which is
124
+ * SCOPED sessions whose nav is filtered by their projected `routes`, which is
125
125
  * exactly right — and the gate-off synthetic projection (`admin: true`, empty
126
126
  * `routes`) still reads as full, so an AC-disabled deployment shows everything.
127
127
  */
@@ -143,8 +143,8 @@ function pathUnderPrefix(path: string, prefix: string): boolean {
143
143
  /**
144
144
  * Whether the projection reaches ANY route under one of `prefixes` — the
145
145
  * declarative test behind nav filtering and plugin `requiredCapabilities`. A
146
- * concrete `routes[].path` covers a prefix when it is that prefix or a
147
- * segment-nested descendant of it; a dynamic `route_patterns[]` entry covers a
146
+ * `routes[].path` (a concrete path, or a route template such as `/api/presets/{name}`)
147
+ * covers a prefix when it is that prefix or a segment-nested descendant of it; a dynamic `route_patterns[]` entry covers a
148
148
  * prefix through its served `open_prefix` — the literal the server rules every
149
149
  * path starting with is reachable — when it overlaps the prefix at a segment
150
150
  * boundary. A `null` open prefix (a pattern whose matched set is not a clean
@@ -167,14 +167,16 @@ export function coversAnyRoute(projection: MeProjection, prefixes: readonly stri
167
167
 
168
168
  /**
169
169
  * Whether the projection reaches a specific WRITE route — an EXACT `path` whose
170
- * admitted `methods` include `method`. This is the method-aware companion to
170
+ * admitted `methods` include `method`. A templated route is listed under its template
171
+ * path (`/api/presets/{name}`) with the methods the caller's grant admits on a
172
+ * representative instance of it, so a caller names the template on both sides. This is the method-aware companion to
171
173
  * {@link coversAnyRoute}: a read/nav surface gates path-only (any method reaching a
172
174
  * prefix means the surface is navigable), but an action that issues `method path`
173
175
  * must gate on the method too, or a caller whose jq fence admits only GET on `path`
174
176
  * would be shown a control that 403s on submit (projection ⊆ gate).
175
177
  *
176
- * Only concrete `routes` are consulted: dynamic `route_patterns` carry no method, so
177
- * no write capability can be soundly inferred from them, and a synthetic TOTAL
178
+ * Only `routes` are consulted: dynamic `route_patterns` carry no method, so no write
179
+ * capability can be soundly inferred from them, and a synthetic TOTAL
178
180
  * (admin) projection carries no concrete `routes` at all — callers short-circuit it
179
181
  * with {@link isFullProjection} before reaching here. Under-showing is safe (the
180
182
  * server stays the authority); over-showing an action the gate denies is the bug.
@@ -191,10 +193,11 @@ export function coversRoute(projection: MeProjection, path: string, method: stri
191
193
  * `method path`. While the projection is loading/failed it is `false` — a write control
192
194
  * never enables before the gate is known (fail closed; not-ready ⇒ disabled/read-only).
193
195
  *
194
- * Only concrete `routes` carry methods, so a DYNAMIC (templated) write route — one the
195
- * projection can represent only as a method-less `route_patterns` row — reads `false` for
196
- * every non-admin caller and degrades that control to read-only. Under-showing is safe
197
- * (the server stays the authority); over-showing a write the gate denies is the bug.
196
+ * A templated write route is gated by its template path: the projection lists it with
197
+ * the methods the caller's grant admits on a representative instance. A credential fenced
198
+ * per instance is decided at the server, which answers 403 on submit where the fence
199
+ * denies that instance. Under-showing is safe (the server stays the authority);
200
+ * over-showing a write the gate denies is the bug.
198
201
  */
199
202
  export function coversWrite(state: CapabilityState, path: string, method: string): boolean {
200
203
  return (
@@ -217,3 +220,49 @@ export function useCanWrite(path: string, method = 'POST'): boolean {
217
220
  const { state } = useCapabilities();
218
221
  return coversWrite(state, path, method);
219
222
  }
223
+
224
+ /** The session's own identity, as the execution-key pickers offer it. */
225
+ export interface OwnIdentity {
226
+ readonly userId: string;
227
+ /** 'account' when the session is a principal itself (no owner claim, a principal row): the bind check refuses
228
+ * it. 'key' otherwise: an owned api key, or a key with no owner. */
229
+ readonly kind: 'key' | 'account';
230
+ readonly admin: boolean;
231
+ }
232
+
233
+ /** How a form binds the execution key a stored record runs as, read from the session's projection. */
234
+ export type ExecutionKeyBinding =
235
+ /** The projection is not ready (loading, or failed: the shell shows its own error); the form cannot submit. */
236
+ | { readonly mode: 'pending' }
237
+ /** Access control is off: no identity is checked, no key field is shown, a blank key is sent as `fillKey`. */
238
+ | { readonly mode: 'unchecked'; readonly fillKey: string }
239
+ /** Access control is on: the key list, plus the session's own identity. */
240
+ | { readonly mode: 'checked'; readonly own: OwnIdentity };
241
+
242
+ /** The execution-key binding a capability state implies. */
243
+ export function executionKeyBindingOf(state: CapabilityState): ExecutionKeyBinding {
244
+ if (state.status !== 'ready') return { mode: 'pending' };
245
+ const { projection } = state;
246
+ if (!projection.access_control_enabled) {
247
+ return { mode: 'unchecked', fillKey: projection.user_id };
248
+ }
249
+ return {
250
+ mode: 'checked',
251
+ own: {
252
+ userId: projection.user_id,
253
+ kind: projection.owner_user_id === null && projection.principal !== null ? 'account' : 'key',
254
+ admin: projection.admin,
255
+ },
256
+ };
257
+ }
258
+
259
+ /** The execution-key binding of the live session. */
260
+ export function useExecutionKeyBinding(): ExecutionKeyBinding {
261
+ const { state } = useCapabilities();
262
+ return useMemo(() => executionKeyBindingOf(state), [state]);
263
+ }
264
+
265
+ /** The key a write sends: the form's value; with access control off and a blank value, the fill key. */
266
+ export function boundExecutionKey(value: string, binding: ExecutionKeyBinding): string {
267
+ return binding.mode === 'unchecked' && value.trim() === '' ? binding.fillKey : value;
268
+ }
package/src/index.ts CHANGED
@@ -42,6 +42,8 @@ import './components/components.css';
42
42
  // -- Plugin API --------------------------------------------------------------
43
43
  export type {
44
44
  ChannelConversationRoute,
45
+ HolderLinkContribution,
46
+ HolderLinkTarget,
45
47
  NavEntryContribution,
46
48
  NavEntrySection,
47
49
  PageContribution,
@@ -50,6 +52,7 @@ export type {
50
52
  PluginEntry,
51
53
  PluginPageParamsSchema,
52
54
  PluginPageProps,
55
+ RegisteredHolderLink,
53
56
  RegisteredNavEntry,
54
57
  RegisteredPage,
55
58
  RegisteredRoutePanel,
@@ -65,7 +68,7 @@ export type {
65
68
  export type { VersionGateResult } from './plugin/version';
66
69
  export { checkPluginApiVersion, STUDIO_PLUGIN_API_VERSION } from './plugin/version';
67
70
  /** What `useApi().runTool` resolves with: the run-tool door's answer, read by its `kind`. */
68
- export type { RunAnswer } from '@tai42/api-client';
71
+ export type { ReferenceHolder, RunAnswer } from '@tai42/api-client';
69
72
 
70
73
  // -- Hooks -------------------------------------------------------------------
71
74
  export type { ApiClient } from './hooks/useApi';
@@ -74,8 +77,14 @@ export type { AuthState } from './hooks/useAuth';
74
77
  export { AuthProvider, useAuth } from './hooks/useAuth';
75
78
  export type { Breakpoint, BreakpointState } from './hooks/useBreakpoint';
76
79
  export { useBreakpoint } from './hooks/useBreakpoint';
77
- export type { CapabilityContextValue, CapabilityState } from './hooks/useCapabilities';
80
+ export type {
81
+ CapabilityContextValue,
82
+ CapabilityState,
83
+ ExecutionKeyBinding,
84
+ OwnIdentity,
85
+ } from './hooks/useCapabilities';
78
86
  export {
87
+ boundExecutionKey,
79
88
  CapabilityProvider,
80
89
  coversAnyRoute,
81
90
  coversRoute,
@@ -83,6 +92,7 @@ export {
83
92
  isFullProjection,
84
93
  useCanWrite,
85
94
  useCapabilities,
95
+ useExecutionKeyBinding,
86
96
  } from './hooks/useCapabilities';
87
97
  export type {
88
98
  InteractionsStreamOptions,
@@ -118,6 +128,7 @@ export type {
118
128
  NavigationContextValue,
119
129
  NavigationGuardHandler,
120
130
  PageProps,
131
+ PluginHolderTarget,
121
132
  PluginNavigateOptions,
122
133
  PluginSearch,
123
134
  RouteSearch,
@@ -141,7 +152,7 @@ export {
141
152
  export { downloadBlob } from './download';
142
153
  export type { EnvRef } from './env-markers';
143
154
  export { collectEnvRefs, ENV_MARKER_PREFIX, formatEnvMarker, parseEnvMarker } from './env-markers';
144
- export { errorMessage } from './errors';
155
+ export { errorMessage, isForbiddenError } from './errors';
145
156
 
146
157
  // -- Disabled-feature idiom (one helper + component, every consumer) ----------
147
158
  export type { FeatureDisabledProps } from './feature-disabled';
@@ -149,8 +160,21 @@ export { FeatureDisabled, featureDisabledMessage, isFeatureDisabled } from './fe
149
160
 
150
161
  // -- Cross-feature query keys ------------------------------------------------
151
162
  export {
163
+ agentsListKey,
164
+ channelsListKey,
165
+ envConfigKey,
152
166
  extensionsQueryKey,
167
+ installedMarketplacePluginsKey,
168
+ marketplacePluginKey,
169
+ mcpAdvertisedToolsKey,
170
+ mcpConfigSchemaKey,
171
+ mcpEnvRefsKey,
172
+ mcpExtensionsKey,
173
+ mcpFailedKey,
174
+ mcpStatusKey,
175
+ preservedManifestKey,
153
176
  statesListKey,
177
+ stateTemplateRefereesKey,
154
178
  stateTemplatesKey,
155
179
  subMcpKey,
156
180
  tokensPayloadKey,
@@ -167,6 +191,8 @@ export { effectiveHidden, hiddenToolNames } from './tool-visibility';
167
191
  export { mergeToolBadges, toolBadgesByName } from './tool-badges';
168
192
 
169
193
  // -- Design system -----------------------------------------------------------
194
+ export type { ActionMenuItem, ActionMenuProps } from './components/action-menu';
195
+ export { ActionMenu } from './components/action-menu';
170
196
  export type { BadgeProps } from './components/badge';
171
197
  export { Badge } from './components/badge';
172
198
  export type { CheckboxProps } from './components/checkbox';
@@ -298,10 +324,19 @@ export {
298
324
  FolderPicker,
299
325
  FolderRow,
300
326
  } from './components/folder-nav';
327
+ export type {
328
+ HolderHref,
329
+ HolderListProps,
330
+ HolderPreviewProps,
331
+ HolderPreviewState,
332
+ } from './components/holder-list';
333
+ export { HolderList, HolderPreview, previewHolders, useHolderHref } from './components/holder-list';
301
334
  export type { HeadingLevel, MarkdownBlock, MarkdownProps } from './components/markdown';
302
335
  export { Markdown, parseMarkdown } from './components/markdown';
303
336
  export type { OpenTargetOptions, OpenTargetProps } from './components/open-target';
304
337
  export { openTargetProps } from './components/open-target';
338
+ export type { RecycleRollSectionProps } from './components/recycle-roll';
339
+ export { RecycleRollSection } from './components/recycle-roll';
305
340
  export type {
306
341
  MediaDocumentProps,
307
342
  MediaImageProps,
@@ -316,21 +351,28 @@ export {
316
351
  MediaPlayer,
317
352
  resolveMediaSrc,
318
353
  } from './components/served-media';
354
+ export type { StepperProps, StepperStatus, StepperStep } from './components/stepper';
355
+ export { Stepper } from './components/stepper';
356
+ // The marketplace install UI; its directory index lists the published names.
357
+ export * from './marketplace-install';
319
358
  // The URL half of the link-safety pair: `isSafeHttpUrl` answers yes/no, this one
320
359
  // hands back the parsed URL, so a caller that needs the URL does not re-parse it.
360
+ export type { AgentListQuery, AgentPickerProps } from './components/agent-picker';
361
+ export { AgentPicker } from './components/agent-picker';
362
+ export type { ChannelListQuery, ChannelPickerProps } from './components/channel-picker';
363
+ export { channelIdentityHint, ChannelPicker } from './components/channel-picker';
321
364
  export type { CopyFieldProps } from './components/copy-field';
322
365
  export { CopyField } from './components/copy-field';
323
366
  export type { ExecutionKeyPickerProps, ExecutionKeyQuery } from './components/execution-key-picker';
324
- export { ExecutionKeyPicker, isExecutionKeyListEmpty } from './components/execution-key-picker';
367
+ export {
368
+ canHoldExecutionKey,
369
+ ExecutionKeyPicker,
370
+ isExecutionKeyListEmpty,
371
+ } from './components/execution-key-picker';
325
372
  export type { ExtensionComboBuilderProps } from './components/extension-combo-builder';
326
373
  export { ExtensionComboBuilder } from './components/extension-combo-builder';
327
- export type { ExtensionFamily, ExtensionKindGroup } from './components/extension-grouping';
328
- export {
329
- baseNameOf,
330
- groupByKind,
331
- groupIntoFamilies,
332
- kindVariant,
333
- } from './components/extension-grouping';
374
+ export type { ExtensionKindGroup } from './components/extension-grouping';
375
+ export { groupByKind } from './components/extension-grouping';
334
376
  export type { ExtensionPickerProps } from './components/extension-picker';
335
377
  export { ExtensionPicker } from './components/extension-picker';
336
378
  export type { ExternalLinkButtonProps } from './components/external-link';
@@ -347,7 +389,7 @@ export {
347
389
  } from './components/subject-section';
348
390
  export type { TokenName } from './components/tokens';
349
391
  export { TOKEN_NAMES } from './components/tokens';
350
- export type { ToolPickerProps } from './components/tool-picker';
392
+ export type { ToolListQuery, ToolPickerProps } from './components/tool-picker';
351
393
  export { BADGES_NOTE, ToolPicker } from './components/tool-picker';
352
394
  export type {
353
395
  VersionHistoryEntry,
@@ -374,7 +416,6 @@ export {
374
416
  DashboardIcon,
375
417
  DatabaseIcon,
376
418
  EditIcon,
377
- ExtensionsIcon,
378
419
  ExternalLinkIcon,
379
420
  EyeIcon,
380
421
  EyeOffIcon,
@@ -406,7 +447,6 @@ export {
406
447
  StorageIcon,
407
448
  SunIcon,
408
449
  SystemIcon,
409
- TemplatesIcon,
410
450
  ToolsIcon,
411
451
  UnplugIcon,
412
452
  XCircleIcon,
@@ -0,0 +1,59 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import { Checkbox } from '../components/checkbox';
4
+ import { Field } from '../components/field';
5
+ import { TextInput } from '../components/inputs';
6
+
7
+ /**
8
+ * One input + secret toggle per required env var, fully controlled. `requiredSecret`
9
+ * is the server's per-var secret-ness: a `true` var (an OAuth client secret) is
10
+ * LOCKED on — the server masks it regardless, so an off toggle would lie — while a
11
+ * `false` var starts off and stays free. `hints` names the item that needs each var.
12
+ */
13
+ export function EnvVarFields({
14
+ requiredVars,
15
+ values,
16
+ secret,
17
+ requiredSecret,
18
+ hints,
19
+ onChangeValue,
20
+ onToggleSecret,
21
+ }: {
22
+ readonly requiredVars: readonly string[];
23
+ readonly values: Record<string, string>;
24
+ readonly secret: Record<string, boolean>;
25
+ readonly requiredSecret?: Record<string, boolean>;
26
+ readonly hints?: Record<string, string>;
27
+ readonly onChangeValue: (name: string, value: string) => void;
28
+ readonly onToggleSecret: (name: string, checked: boolean) => void;
29
+ }): ReactNode {
30
+ return (
31
+ <>
32
+ {requiredVars.map((name) => {
33
+ const locked = requiredSecret?.[name] === true;
34
+ const hint = hints?.[name];
35
+ return (
36
+ <div key={name} className="tai-stack tai-stack-2">
37
+ <Field label={name}>
38
+ <TextInput
39
+ value={values[name] ?? ''}
40
+ onChange={(event) => {
41
+ onChangeValue(name, event.currentTarget.value);
42
+ }}
43
+ />
44
+ </Field>
45
+ {hint !== undefined ? <span className="tai-muted">Required by {hint}</span> : null}
46
+ <Checkbox
47
+ checked={secret[name] === true}
48
+ disabled={locked}
49
+ onCheckedChange={(checked) => {
50
+ onToggleSecret(name, checked);
51
+ }}
52
+ label="Store as secret"
53
+ />
54
+ </div>
55
+ );
56
+ })}
57
+ </>
58
+ );
59
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The marketplace install UI the package publishes: the install and update forms,
3
+ * the install report, and the helpers a screen composes them with. The Marketplace
4
+ * detail screen and the Connectors flow render the same forms and the same report.
5
+ */
6
+ export type { InstallReportProps } from './install-report';
7
+ export { InstallReport } from './install-report';
8
+ export type { MountInstallDialogProps } from './mount-install';
9
+ export { MountInstallDialog, MountInstallForm } from './mount-install';
10
+ export { deliveryOf } from './plugin-detail';
11
+ export type { PluginInstallProps } from './plugin-install';
12
+ export { INSTALL_NO_GRANT, PluginInstallDialog, PluginInstallPanel } from './plugin-install';
13
+ export type { RouteItem } from './route-items';
14
+ export { routeItemsOf } from './route-items';
15
+ export { useDebouncedValue } from './use-debounced-value';
16
+ export type { InstallAction, InstallActionState } from './use-install-action';
17
+ export { useInstallAction } from './use-install-action';
@@ -0,0 +1,39 @@
1
+ /** The secret bands for the install env form: which collected vars are marked
2
+ * secret, and which are LOCKED secret (the server masks them regardless). */
3
+
4
+ /** One required-env entry as the install preview reports it. */
5
+ export interface PreviewEnvRequirement {
6
+ readonly name: string;
7
+ readonly secret: boolean;
8
+ }
9
+
10
+ export interface InstallEnvBands {
11
+ /** Per var, whether its value is treated as a secret (preview, detail, or override). */
12
+ readonly secretMap: Record<string, boolean>;
13
+ /** Per var, whether the secret mark is LOCKED on (preview or detail says secret). */
14
+ readonly lockedSecret: Record<string, boolean>;
15
+ }
16
+
17
+ /**
18
+ * Fold the preview's authority, the detail-derived fallback, and the operator's
19
+ * per-var override into the two bands the env fields render from. A var the preview
20
+ * or the detail marks secret is LOCKED on (an off toggle would lie, since the server
21
+ * masks it regardless); the override only turns a free var on.
22
+ */
23
+ export function installEnvBands(
24
+ missingVars: readonly string[],
25
+ previewRequired: readonly PreviewEnvRequirement[],
26
+ detailSecret: Record<string, boolean>,
27
+ override: Record<string, boolean>,
28
+ ): InstallEnvBands {
29
+ const previewSecret: Record<string, boolean> = {};
30
+ for (const req of previewRequired) previewSecret[req.name] = req.secret;
31
+ const secretMap: Record<string, boolean> = {};
32
+ const lockedSecret: Record<string, boolean> = {};
33
+ for (const name of missingVars) {
34
+ const locked = previewSecret[name] === true || detailSecret[name] === true;
35
+ lockedSecret[name] = locked;
36
+ secretMap[name] = locked || override[name] === true;
37
+ }
38
+ return { secretMap, lockedSecret };
39
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Pure model for the route-mounting install flow: the env value/secret split, the
3
+ * per-var secret band derivation, and the install/update body assembly.
4
+ */
5
+ import type { MarketplaceInstallPreview } from '@tai42/api-client';
6
+
7
+ /** The install/update body fields the mount form contributes. */
8
+ export interface InstallExtras {
9
+ route_mounts: Record<string, string>;
10
+ accept_public_routes: boolean;
11
+ env?: Record<string, string>;
12
+ secret_keys?: string[];
13
+ }
14
+
15
+ /**
16
+ * The env value + secret-key split from the collected inputs: a blank field is
17
+ * omitted (the deployment may already provide it); a filled field is marked secret
18
+ * only when its toggle is ON.
19
+ *
20
+ * The toggle state is SEEDED from the server's per-var `required_env[].secret`, so
21
+ * secret-ness comes from that authority, not a blanket default: a `secret: true`
22
+ * var (an OAuth client secret) is masked, a `secret: false` var (a client id) is
23
+ * not unless the operator turns it on. Keying off `=== true` honors an off toggle
24
+ * and never force-masks — an un-seeded var stays out of the secret band.
25
+ */
26
+ export function collectEnv(
27
+ requiredVars: readonly string[],
28
+ values: Record<string, string>,
29
+ secret: Record<string, boolean>,
30
+ ): { env: Record<string, string>; secretKeys: string[] } {
31
+ const env: Record<string, string> = {};
32
+ const secretKeys: string[] = [];
33
+ for (const name of requiredVars) {
34
+ const value = values[name] ?? '';
35
+ if (value === '') continue;
36
+ env[name] = value;
37
+ if (secret[name] === true) secretKeys.push(name);
38
+ }
39
+ return { env, secretKeys };
40
+ }
41
+
42
+ /**
43
+ * The effective per-var secret band for the vars to collect: the SERVER's per-var
44
+ * `required_env[].secret` from the preview (the authority), OR the caller-supplied
45
+ * `requiredEnvSecret`, OR an operator override.
46
+ */
47
+ export function deriveEnvSecretMap(
48
+ envToCollect: readonly string[],
49
+ preview: MarketplaceInstallPreview | undefined,
50
+ requiredEnvSecret: Record<string, boolean> | undefined,
51
+ envSecretOverride: Record<string, boolean>,
52
+ ): Record<string, boolean> {
53
+ const previewEnvSecret: Record<string, boolean> = {};
54
+ for (const req of preview?.required_env ?? []) previewEnvSecret[req.name] = req.secret;
55
+ return Object.fromEntries(
56
+ envToCollect.map((name) => [
57
+ name,
58
+ previewEnvSecret[name] === true ||
59
+ requiredEnvSecret?.[name] === true ||
60
+ envSecretOverride[name] === true,
61
+ ]),
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Assemble the install/update body. INSTALL sends every base (declared default is
67
+ * correct). UPDATE sends ONLY the items whose base changed from its current stored
68
+ * base — omitted items are preserved by the server's stored-mount precedence, so a
69
+ * plugin installed at a non-default base is never reset to default on an edit-free
70
+ * update. Collected env is attached only when at least one value was filled.
71
+ */
72
+ export function buildInstallExtras(
73
+ verb: 'Install' | 'Update',
74
+ bases: Record<string, string>,
75
+ seededBases: Record<string, string>,
76
+ accepted: boolean,
77
+ envToCollect: readonly string[],
78
+ envValues: Record<string, string>,
79
+ envSecretMap: Record<string, boolean>,
80
+ ): InstallExtras {
81
+ const route_mounts =
82
+ verb === 'Update'
83
+ ? Object.fromEntries(
84
+ Object.entries(bases).filter(([name, base]) => base !== seededBases[name]),
85
+ )
86
+ : bases;
87
+ const extras: InstallExtras = { route_mounts, accept_public_routes: accepted };
88
+ if (envToCollect.length > 0) {
89
+ const { env, secretKeys } = collectEnv(envToCollect, envValues, envSecretMap);
90
+ if (Object.keys(env).length > 0) {
91
+ extras.env = env;
92
+ extras.secret_keys = secretKeys;
93
+ }
94
+ }
95
+ return extras;
96
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The fleet side of an install, update or uninstall: the reload fan-out report and,
3
+ * when the reload recycled workers, the roll. The host folds the receipt's
4
+ * `reload.fanout` / `awaiting_respawn` / `span_release_error` into `summary` with
5
+ * `summarizeFleetFanout` and passes `reload.recycle` as `recycle`.
6
+ */
7
+ import type { FleetReportSummary, RecycleRoll } from '@tai42/api-client';
8
+ import { type CSSProperties, type ReactNode, useId } from 'react';
9
+
10
+ import { FleetReport } from '../components/fleet-report';
11
+ import { RecycleRollSection } from '../components/recycle-roll';
12
+
13
+ export interface InstallReportProps {
14
+ readonly summary: FleetReportSummary | null;
15
+ readonly recycle: RecycleRoll | null;
16
+ }
17
+
18
+ const sectionTitleStyle: CSSProperties = {
19
+ margin: '0 0 var(--tai-space-2)',
20
+ fontSize: 'var(--tai-text-md)',
21
+ color: 'var(--tai-color-text)',
22
+ };
23
+
24
+ const sectionIntroStyle: CSSProperties = {
25
+ margin: '0 0 var(--tai-space-3)',
26
+ color: 'var(--tai-color-text-muted)',
27
+ fontSize: 'var(--tai-text-sm)',
28
+ };
29
+
30
+ export function InstallReport({ summary, recycle }: InstallReportProps): ReactNode {
31
+ const headingId = useId();
32
+ return (
33
+ <div className="tai-stack tai-stack-3">
34
+ <FleetReport summary={summary} />
35
+ {recycle !== null ? (
36
+ <section aria-labelledby={headingId}>
37
+ <h4 id={headingId} style={sectionTitleStyle}>
38
+ Worker recycle
39
+ </h4>
40
+ <p style={sectionIntroStyle}>
41
+ Some changed settings take effect only in a fresh worker process, so the fleet recycled
42
+ its workers one at a time.
43
+ </p>
44
+ <RecycleRollSection
45
+ recycle={recycle.recycle}
46
+ fresh={recycle.fresh}
47
+ stopped={recycle.recycle_stopped}
48
+ writerNote="this worker recycles itself after replying"
49
+ />
50
+ </section>
51
+ ) : null}
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The two frames an install form renders in: a dialog (the confirm shell) or a panel
3
+ * in place (its own `<form>` with Cancel and the submit button), so a step can embed
4
+ * the form without a dialog inside a dialog. The failure of the last call renders
5
+ * inside the frame and the frame stays open.
6
+ */
7
+ import type { ReactNode, SyntheticEvent } from 'react';
8
+
9
+ import { ConfirmDialog } from '../components/confirm-dialog';
10
+ import { Button, ErrorState, Spinner } from '../components/primitives';
11
+ import { errorMessage } from '../errors';
12
+ import { FeatureDisabled, featureDisabledMessage, isFeatureDisabled } from '../feature-disabled';
13
+
14
+ export type InstallLayout = 'panel' | 'dialog';
15
+
16
+ /**
17
+ * The muted note for a store-off refusal (an OFF feature is a state, not an error),
18
+ * or `undefined` for any other value.
19
+ */
20
+ export function storeOffNote(error: unknown): ReactNode {
21
+ if (!isFeatureDisabled(error)) return undefined;
22
+ return <FeatureDisabled feature="Marketplace installs" message={featureDisabledMessage(error)} />;
23
+ }
24
+
25
+ /** A failed call's surface: the store-off note, else the server message as an alert. */
26
+ export function InstallFailure({ error }: { readonly error: unknown }): ReactNode {
27
+ return storeOffNote(error) ?? <ErrorState message={errorMessage(error)} />;
28
+ }
29
+
30
+ export interface InstallShellProps {
31
+ readonly layout: InstallLayout;
32
+ readonly title: string;
33
+ readonly submitLabel: string;
34
+ readonly pendingLabel: string;
35
+ readonly isPending: boolean;
36
+ /** The loud failure; `null` while there is none. */
37
+ readonly error: unknown;
38
+ /** A muted note that takes the failure slot and blocks the submit. */
39
+ readonly disabledNote?: ReactNode;
40
+ readonly submitDisabled?: boolean;
41
+ readonly onSubmit: () => void;
42
+ readonly onDismiss: () => void;
43
+ readonly children: ReactNode;
44
+ }
45
+
46
+ export function InstallShell({
47
+ layout,
48
+ title,
49
+ submitLabel,
50
+ pendingLabel,
51
+ isPending,
52
+ error,
53
+ disabledNote,
54
+ submitDisabled = false,
55
+ onSubmit,
56
+ onDismiss,
57
+ children,
58
+ }: InstallShellProps): ReactNode {
59
+ const errorText = error === null || error === undefined ? null : errorMessage(error);
60
+ if (layout === 'dialog') {
61
+ return (
62
+ <ConfirmDialog
63
+ title={title}
64
+ confirmLabel={submitLabel}
65
+ pendingLabel={pendingLabel}
66
+ confirmVariant="primary"
67
+ isPending={isPending}
68
+ error={errorText}
69
+ disabledNote={disabledNote}
70
+ confirmDisabled={submitDisabled}
71
+ onConfirm={onSubmit}
72
+ onClose={onDismiss}
73
+ >
74
+ {children}
75
+ </ConfirmDialog>
76
+ );
77
+ }
78
+ const blocked =
79
+ isPending || submitDisabled || (disabledNote !== undefined && disabledNote !== null);
80
+ const handleSubmit = (event: SyntheticEvent<HTMLFormElement>): void => {
81
+ event.preventDefault();
82
+ if (!blocked) onSubmit();
83
+ };
84
+ return (
85
+ <form aria-label={title} className="tai-stack tai-stack-3" onSubmit={handleSubmit}>
86
+ {children}
87
+ {disabledNote ?? (errorText !== null ? <ErrorState message={errorText} /> : null)}
88
+ <div className="tai-dialog-actions">
89
+ <Button type="button" onClick={onDismiss}>
90
+ Cancel
91
+ </Button>
92
+ <Button type="submit" variant="primary" disabled={blocked}>
93
+ {isPending ? <Spinner label={pendingLabel} /> : null}
94
+ {submitLabel}
95
+ </Button>
96
+ </div>
97
+ </form>
98
+ );
99
+ }