@jini-ai/admin 0.4.11 → 0.4.13

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 (165) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/agent-plugins/controllers/agent-plugin-files.controller.js +14 -9
  3. package/dist/agent-plugins/controllers/agent-plugins.controller.js +12 -9
  4. package/dist/agent-plugins/package-file-tree.js +54 -51
  5. package/dist/agent-plugins/react/components/AgentPluginRow.d.ts +1 -1
  6. package/dist/agent-plugins/react/components/AgentPluginRow.js +9 -2
  7. package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.d.ts +5 -5
  8. package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.js +14 -5
  9. package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.d.ts +15 -13
  10. package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.js +54 -40
  11. package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.d.ts +13 -13
  12. package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.js +23 -11
  13. package/dist/browser/navigation.js +44 -27
  14. package/dist/comments/adapters/memory.js +30 -12
  15. package/dist/comments/conformance/comments-api.conformance.js +55 -33
  16. package/dist/comments/controllers/comment-queue.controller.js +13 -4
  17. package/dist/comments/react/hooks/use-comment-queue.hooks.d.ts +17 -2
  18. package/dist/comments/react/hooks/use-comment-queue.hooks.js +79 -30
  19. package/dist/comments/react/hooks/use-comment-settings.hooks.js +9 -5
  20. package/dist/comments/react/index.d.ts +2 -1
  21. package/dist/comments/react/index.js +1 -1
  22. package/dist/consent/react/ConsentFields.d.ts +35 -0
  23. package/dist/consent/react/ConsentFields.js +74 -0
  24. package/dist/consent/react/ConsentPreviewFrame.d.ts +14 -0
  25. package/dist/consent/react/ConsentPreviewFrame.js +14 -0
  26. package/dist/consent/react/ConsentSettingsPanel.d.ts +27 -4
  27. package/dist/consent/react/ConsentSettingsPanel.js +6 -17
  28. package/dist/consent/react/index.d.ts +6 -1
  29. package/dist/consent/react/index.js +2 -0
  30. package/dist/consent/react/settings-panel.hooks.d.ts +28 -1
  31. package/dist/consent/react/settings-panel.hooks.js +26 -0
  32. package/dist/core/entities/rules.js +126 -81
  33. package/dist/core/manifest/rules.js +10 -10
  34. package/dist/core/module/create-admin.js +195 -117
  35. package/dist/core/ports/redirects.d.ts +8 -6
  36. package/dist/core/ports/redirects.js +8 -6
  37. package/dist/core/ports/seo.d.ts +20 -0
  38. package/dist/core/routing/rules.js +9 -4
  39. package/dist/core/transport/http.js +37 -20
  40. package/dist/forms/adapters/memory.js +22 -11
  41. package/dist/forms/conformance/forms-api.conformance.d.ts +6 -4
  42. package/dist/forms/conformance/forms-api.conformance.js +42 -46
  43. package/dist/forms/field-settings.js +150 -115
  44. package/dist/forms/html-rules.js +45 -28
  45. package/dist/forms/react/components/FieldSettingsRows.d.ts +22 -0
  46. package/dist/forms/react/components/FieldSettingsRows.js +29 -0
  47. package/dist/forms/react/components/FieldSettingsSection.d.ts +1 -1
  48. package/dist/forms/react/components/FieldSettingsSection.js +5 -6
  49. package/dist/forms/react/components/FormEditorFieldsBody.d.ts +1 -1
  50. package/dist/forms/react/components/FormEditorFieldsBody.js +4 -15
  51. package/dist/forms/react/components/FormEditorFieldsBodyParts.d.ts +18 -0
  52. package/dist/forms/react/components/FormEditorFieldsBodyParts.js +32 -0
  53. package/dist/forms/react/components/FormSubmissions.d.ts +1 -1
  54. package/dist/forms/react/components/FormSubmissions.js +4 -20
  55. package/dist/forms/react/components/FormSubmissionsTable.d.ts +19 -0
  56. package/dist/forms/react/components/FormSubmissionsTable.js +22 -0
  57. package/dist/media/adapters/memory.js +31 -26
  58. package/dist/media/controllers/edit-media.controller.js +2 -1
  59. package/dist/media/controllers/library.controller.js +48 -35
  60. package/dist/media/controllers/providers.controller.js +2 -1
  61. package/dist/media/html-attributes.js +29 -15
  62. package/dist/media/index.d.ts +1 -1
  63. package/dist/media/index.js +1 -1
  64. package/dist/media/react/components/MediaIcons.d.ts +0 -3
  65. package/dist/media/react/components/MediaIcons.js +0 -9
  66. package/dist/media/react/components/MediaLightbox.d.ts +2 -1
  67. package/dist/media/react/components/MediaLightbox.js +11 -5
  68. package/dist/media/react/hooks/LibraryTab.hooks.d.ts +55 -45
  69. package/dist/media/react/hooks/LibraryTab.hooks.js +107 -70
  70. package/dist/media/react/hooks/MediaCard.hooks.d.ts +27 -31
  71. package/dist/media/react/hooks/MediaCard.hooks.js +52 -26
  72. package/dist/media/react/hooks/MediaGrid.hooks.d.ts +1 -1
  73. package/dist/media/react/hooks/MediaLightbox.hooks.d.ts +10 -14
  74. package/dist/media/react/hooks/MediaLightbox.hooks.js +9 -56
  75. package/dist/media/react/hooks/MediaPage.hooks.d.ts +6 -0
  76. package/dist/media/react/hooks/MediaPage.hooks.js +29 -12
  77. package/dist/media/react/hooks/ProvidersTab.hooks.d.ts +33 -33
  78. package/dist/media/react/hooks/ProvidersTab.hooks.js +119 -79
  79. package/dist/media/react/hooks/UploadButton.hooks.d.ts +7 -5
  80. package/dist/media/react/hooks/UploadButton.hooks.js +36 -24
  81. package/dist/media/react/index.js +4 -0
  82. package/dist/media/react/tabs/LibraryTab.d.ts +1 -1
  83. package/dist/media/react/tabs/LibraryTab.js +2 -2
  84. package/dist/media/rules.d.ts +7 -0
  85. package/dist/media/rules.js +25 -2
  86. package/dist/react/components/ConfirmDialog/ConfirmDialog.js +5 -8
  87. package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.d.ts +20 -0
  88. package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.js +24 -0
  89. package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +16 -7
  90. package/dist/react/components/RowMenu/RowMenu.js +16 -10
  91. package/dist/react/components/Sidebar.js +32 -26
  92. package/dist/react/entities/EntityEdit.js +17 -14
  93. package/dist/react/entities/EntityField.d.ts +5 -2
  94. package/dist/react/entities/EntityField.js +38 -23
  95. package/dist/react/entities/EntityList.js +6 -5
  96. package/dist/react/entities/RelationTruncationNotice.d.ts +9 -0
  97. package/dist/react/entities/RelationTruncationNotice.js +9 -0
  98. package/dist/react/entities/use-entity-edit.d.ts +4 -2
  99. package/dist/react/entities/use-entity-edit.js +52 -33
  100. package/dist/react/shell/AdminShell.js +40 -23
  101. package/dist/react/shell/model.d.ts +4 -1
  102. package/dist/react/shell/model.js +3 -2
  103. package/dist/react/shell/use-admin-shell-session.js +25 -26
  104. package/dist/redirects/adapters/memory.js +9 -2
  105. package/dist/redirects/conformance/redirects-api.conformance.js +66 -36
  106. package/dist/redirects/react/hooks/use-import-redirects-form.hooks.d.ts +1 -1
  107. package/dist/redirects/react/hooks/use-import-redirects-form.hooks.js +10 -3
  108. package/dist/redirects/react/hooks/wired.hooks.d.ts +1 -1
  109. package/dist/security/adapters/http.js +25 -10
  110. package/dist/security/adapters/memory.js +19 -5
  111. package/dist/security/controllers/access-tokens.controller.js +27 -26
  112. package/dist/security/controllers/other-credentials.controller.js +4 -2
  113. package/dist/security/controllers/root-key.controller.js +17 -9
  114. package/dist/security/react/hooks/AccessTokensTab.hooks.d.ts +40 -38
  115. package/dist/security/react/hooks/AccessTokensTab.hooks.js +36 -9
  116. package/dist/security/react/hooks/RootKeyTab.hooks.d.ts +14 -14
  117. package/dist/security/react/hooks/RootKeyTab.hooks.js +12 -3
  118. package/dist/security/react/tabs/RootKeyTab.d.ts +1 -1
  119. package/dist/security/react/tabs/RootKeyTab.js +12 -3
  120. package/dist/security/rules.js +45 -25
  121. package/dist/seo/adapters/http.js +5 -0
  122. package/dist/seo/controllers/seo.controller.js +4 -2
  123. package/dist/seo/messages.en.d.ts +15 -10
  124. package/dist/seo/messages.en.js +15 -10
  125. package/dist/seo/models.d.ts +11 -0
  126. package/dist/seo/react/components/MediaRefField.d.ts +17 -5
  127. package/dist/seo/react/components/MediaRefField.js +31 -4
  128. package/dist/seo/react/hooks/MediaRefField.hooks.js +12 -2
  129. package/dist/seo/react/hooks/SeoPage.hooks.d.ts +1 -0
  130. package/dist/seo/react/hooks/SeoPage.hooks.js +2 -2
  131. package/dist/seo/react/hooks/SeoQuery.hooks.d.ts +1 -0
  132. package/dist/seo/react/hooks/SeoQuery.hooks.js +1 -0
  133. package/dist/seo/react/hooks/use-entry-picker.hooks.js +7 -2
  134. package/dist/seo/react/hooks/use-seo-entry-panel.hooks.js +13 -2
  135. package/dist/seo/react/hooks/use-share-image-warning.hooks.d.ts +4 -1
  136. package/dist/seo/react/hooks/use-share-image-warning.hooks.js +48 -33
  137. package/dist/seo/react/index.d.ts +45 -30
  138. package/dist/seo/react/pages/SeoPage.d.ts +6 -4
  139. package/dist/seo/react/pages/SeoPage.js +51 -29
  140. package/dist/seo/rules.d.ts +91 -0
  141. package/dist/seo/rules.js +161 -6
  142. package/dist/seo/seo.module.d.ts +15 -10
  143. package/dist/source-control/conformance/source-control-api.conformance.js +68 -38
  144. package/dist/source-control/controllers/source-control.controller.js +20 -8
  145. package/dist/source-control/react/hooks/ProvidersTab.hooks.d.ts +2 -1
  146. package/dist/source-control/react/hooks/ProvidersTab.hooks.js +18 -11
  147. package/dist/widgets/conformance/widgets-api.conformance.js +15 -1
  148. package/dist/widgets/controllers/instance-editor.controller.js +33 -24
  149. package/dist/widgets/controllers/library.controller.js +18 -12
  150. package/dist/widgets/controllers/region-editor.controller.js +6 -5
  151. package/dist/widgets/react/hooks/page-views.hooks.d.ts +1 -0
  152. package/dist/widgets/react/hooks/page-views.hooks.js +3 -2
  153. package/dist/widgets/react/hooks/use-widget-regions.hooks.d.ts +1 -1
  154. package/dist/widgets/react/hooks/use-widget-regions.hooks.js +9 -3
  155. package/dist/widgets/react/hooks/use-widgets-library.hooks.js +9 -2
  156. package/dist/widgets/react/pages/WidgetInstanceEditor.d.ts +1 -1
  157. package/dist/widgets/react/pages/WidgetInstanceEditor.js +7 -8
  158. package/dist/widgets/rules.d.ts +14 -2
  159. package/dist/widgets/rules.js +9 -6
  160. package/package.json +19 -10
  161. package/src/themes/default/admin.css +51 -0
  162. package/src/themes/default/chat.css +21 -0
  163. package/src/themes/default/manifest.json +29 -0
  164. package/dist/media/styles.d.ts +0 -4
  165. package/dist/media/styles.js +0 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.13 — 2026-10-22
4
+
5
+ - 2026-10-22: Consent banner editor sections and preview, redirect list fixes, media lightbox via @jini-ai/ui, media provider tab fixes, coverage.
6
+
7
+ ## 0.4.12 — 2026-10-16
8
+
9
+ - SEO: searchable entry picker and grouped per-entry form (6c128f9d); image fields name the file, styled analysis, slug for same-titled entries (bc1dbf0d); one Social preview section with an optional "Customize for X" (ff1a709f); X/Twitter labels, file names with extensions, muted inherited site-default image (4919371d); "Using image from page content" when the share image comes from the page (746457d9).
10
+ - Peers: `@jini-ai/ui` `^0.5.5` (grouped Select) and `@jini-ai/cms` `^0.5.13` (share-image source).
11
+
3
12
  ## 0.4.11 — 2026-10-14
4
13
 
5
14
  - `FieldAttributesDialog` and `SitemapModal` use the neutral `jini-domain-dialog` class instead of a host-named one; hosts style `.jini-domain-dialog` (47b9fa4f).
@@ -1,14 +1,22 @@
1
1
  import { createControllerStore } from '../../core/module/controller-store.js';
2
2
  import { agentPluginFilesSnapshot, selectAgentPluginFile, AGENT_PLUGINS_READ } from '../rules.js';
3
3
  import { agentPluginsMessagesEn as m } from '../messages.en.js';
4
+ const selectedPathIn = ({ files, path }, _optional = {}) => selectAgentPluginFile({ files, path })?.relativePath ?? '';
4
5
  export function createAgentPluginFilesController({ api, permissions = [] }, _optional = {}) {
5
6
  const store = createControllerStore({ initial: { pluginId: null, listing: null, loading: false, error: null, selectedPath: '' } });
6
7
  let generation = 0, pending = null;
7
8
  function cancel() { generation++; pending?.abort(); pending = null; }
9
+ const canRead = () => !store.signal.aborted && permissions.includes(AGENT_PLUGINS_READ);
10
+ function accept({ pluginId, result }, _optional = {}) {
11
+ if (result.pluginId !== pluginId)
12
+ throw new Error(`Files response was for plugin "${result.pluginId}", expected "${pluginId}"`);
13
+ const listing = agentPluginFilesSnapshot({ listing: result });
14
+ store.set({ patch: { listing, selectedPath: selectedPathIn({ files: listing.files, path: '' }) } });
15
+ }
8
16
  return {
9
17
  getSnapshot: store.getSnapshot, subscribe: store.subscribe,
10
18
  async open({ pluginId }, _optional = {}) {
11
- if (store.signal.aborted || !permissions.includes(AGENT_PLUGINS_READ))
19
+ if (!canRead())
12
20
  return;
13
21
  cancel();
14
22
  const ticket = generation, abort = new AbortController();
@@ -16,12 +24,9 @@ export function createAgentPluginFilesController({ api, permissions = [] }, _opt
16
24
  store.set({ patch: { pluginId, listing: null, loading: true, error: null, selectedPath: '' } });
17
25
  try {
18
26
  const result = await api.files({ pluginId }, { signal: abort.signal });
19
- if (ticket !== generation || store.signal.aborted)
20
- return;
21
- if (result.pluginId !== pluginId)
22
- throw new Error('Invalid files response');
23
- const listing = agentPluginFilesSnapshot({ listing: result });
24
- store.set({ patch: { listing, selectedPath: selectAgentPluginFile({ files: listing.files, path: '' })?.relativePath ?? '' } });
27
+ // Disposal cancels first (bumping generation), so a stale ticket also covers a disposed store.
28
+ if (ticket === generation)
29
+ accept({ pluginId, result });
25
30
  }
26
31
  catch {
27
32
  if (ticket === generation)
@@ -34,8 +39,8 @@ export function createAgentPluginFilesController({ api, permissions = [] }, _opt
34
39
  }
35
40
  }
36
41
  },
37
- select({ path }, _optional = {}) { if (store.signal.aborted)
38
- return; store.set({ patch: { selectedPath: selectAgentPluginFile({ files: store.getSnapshot().listing?.files ?? [], path })?.relativePath ?? '' } }); },
42
+ // A disposed store already ignores writes, so no separate disposed check is needed here.
43
+ select({ path }, _optional = {}) { store.set({ patch: { selectedPath: selectedPathIn({ files: store.getSnapshot().listing?.files ?? [], path }) } }); },
39
44
  close(_required, _optional = {}) { cancel(); store.set({ patch: { pluginId: null, listing: null, loading: false, error: null, selectedPath: '' } }); },
40
45
  dispose(_required = {}, _optional = {}) { cancel(); store.set({ patch: { pluginId: null, listing: null, selectedPath: '' } }); store.dispose(); },
41
46
  };
@@ -8,6 +8,12 @@ export function createAgentPluginsController({ api, permissions = [] }, _optiona
8
8
  const canRead = () => !store.signal.aborted && grants.has(AGENT_PLUGINS_READ);
9
9
  const canWrite = () => canRead() && grants.has(AGENT_PLUGINS_WRITE);
10
10
  const rowFor = (id) => store.getSnapshot().plugins?.find(p => p.pluginId === id);
11
+ function acceptList({ plugins }, _optional = {}) {
12
+ const rows = Object.freeze(plugins.map(row => agentPluginSummary({ row })));
13
+ const state = store.getSnapshot(), listed = (id) => rows.some(p => p.pluginId === id);
14
+ const pendingStillEnabled = rows.some(p => p.pluginId === state.pendingDisable?.pluginId && p.enabled);
15
+ store.set({ patch: { plugins: rows, inspectedId: listed(state.inspectedId) ? state.inspectedId : null, pendingDisable: pendingStillEnabled ? state.pendingDisable : null, expandedIds: state.expandedIds.filter(id => listed(id)) } });
16
+ }
11
17
  async function load(_required, _optional = {}) {
12
18
  if (!canRead())
13
19
  return;
@@ -16,13 +22,9 @@ export function createAgentPluginsController({ api, permissions = [] }, _optiona
16
22
  try {
17
23
  const plugins = await api.list({}, { signal: store.signal });
18
24
  // Older reads cannot overwrite accepted writes; each updated row replaces itself once.
19
- if (ticket !== generation || store.signal.aborted)
20
- return;
21
- if (version === revision) {
22
- const rows = Object.freeze(plugins.map(row => agentPluginSummary({ row })));
23
- const state = store.getSnapshot();
24
- store.set({ patch: { plugins: rows, inspectedId: rows.some(p => p.pluginId === state.inspectedId) ? state.inspectedId : null, pendingDisable: rows.some(p => p.pluginId === state.pendingDisable?.pluginId && p.enabled) ? state.pendingDisable : null, expandedIds: state.expandedIds.filter(id => rows.some(p => p.pluginId === id)) } });
25
- }
25
+ // Disposal bumps generation, so a current ticket also means the store is not disposed.
26
+ if (ticket === generation && version === revision)
27
+ acceptList({ plugins });
26
28
  }
27
29
  catch {
28
30
  if (ticket === generation)
@@ -46,17 +48,18 @@ export function createAgentPluginsController({ api, permissions = [] }, _optiona
46
48
  throw new Error('Invalid activation response');
47
49
  revision++;
48
50
  // No optimistic flip: independent rows always splice into the newest snapshot.
51
+ // rowFor found this row above and plugins is never reset to null, so the list exists here.
49
52
  const current = store.getSnapshot();
50
- store.set({ patch: { plugins: Object.freeze((current.plugins ?? []).map(p => p.pluginId === updated.pluginId ? agentPluginSummary({ row: updated }) : p)) } });
53
+ store.set({ patch: { plugins: Object.freeze(current.plugins.map(p => p.pluginId === updated.pluginId ? agentPluginSummary({ row: updated }) : p)) } });
51
54
  return true;
52
55
  }
53
56
  catch {
54
57
  store.set({ patch: { actionError: m.actionError } });
55
- return false;
56
58
  }
57
59
  finally {
58
60
  store.set({ patch: { busyIds: store.getSnapshot().busyIds.filter(id => id !== input.pluginId) } });
59
61
  }
62
+ return false;
60
63
  }
61
64
  return {
62
65
  getSnapshot: store.getSnapshot, subscribe: store.subscribe, load, setEnabled,
@@ -8,20 +8,16 @@
8
8
  */
9
9
  const collator = new Intl.Collator("en", { numeric: true, sensitivity: "base" });
10
10
  /** Files first, then folders, each alphabetical (owner's call, 2026-09-29 — VS Code's default is the reverse). */
11
- function compareNodes(a, b, sorter = collator) {
12
- if (a.kind !== b.kind)
13
- return a.kind === "file" ? -1 : 1;
14
- return sorter.compare(a.name, b.name) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
15
- }
16
- function freeze(folder, prefix, sorter = collator) {
17
- const nodes = [];
18
- for (const [name, child] of folder.folders) {
11
+ function freeze(folder, prefix, sorter) {
12
+ // Two same-kind siblings never share a name (folders are keyed by name, files deduped by path),
13
+ // so a collator tie only needs a code-unit tie-break, never an "equal" result.
14
+ const byName = (a, b) => sorter.compare(a.name, b.name) || (a.name < b.name ? -1 : 1);
15
+ const files = folder.files.map((file) => ({ kind: "file", ...file })).sort(byName);
16
+ const folders = [...folder.folders].map(([name, child]) => {
19
17
  const path = prefix ? `${prefix}/${name}` : name;
20
- nodes.push({ kind: "folder", name, path, children: freeze(child, path, sorter) });
21
- }
22
- for (const file of folder.files)
23
- nodes.push({ kind: "file", ...file });
24
- return nodes.sort((a, b) => compareNodes(a, b, sorter));
18
+ return { kind: "folder", name, path, children: freeze(child, path, sorter) };
19
+ }).sort(byName);
20
+ return [...files, ...folders];
25
21
  }
26
22
  /**
27
23
  * Groups flat package-relative paths into a folder tree. Empty segments (a leading, trailing or
@@ -38,15 +34,7 @@ export function buildPackageFileTree({ paths }, optional = {}) {
38
34
  const fileName = segments.pop();
39
35
  if (!fileName)
40
36
  continue;
41
- let folder = root;
42
- for (const segment of segments) {
43
- let next = folder.folders.get(segment);
44
- if (!next) {
45
- next = { folders: new Map(), files: [] };
46
- folder.folders.set(segment, next);
47
- }
48
- folder = next;
49
- }
37
+ const folder = folderAt({ root, segments });
50
38
  const normalized = [...segments, fileName].join("/");
51
39
  if (seen.has(normalized))
52
40
  continue;
@@ -55,6 +43,19 @@ export function buildPackageFileTree({ paths }, optional = {}) {
55
43
  }
56
44
  return freeze(root, "", optional.locale === undefined ? collator : new Intl.Collator(optional.locale, { numeric: true, sensitivity: "base" }));
57
45
  }
46
+ /** Walks `segments` down from `root`, creating each missing folder on the way. */
47
+ function folderAt({ root, segments }, _optional = {}) {
48
+ let folder = root;
49
+ for (const segment of segments) {
50
+ let next = folder.folders.get(segment);
51
+ if (!next) {
52
+ next = { folders: new Map(), files: [] };
53
+ folder.folders.set(segment, next);
54
+ }
55
+ folder = next;
56
+ }
57
+ return folder;
58
+ }
58
59
  /**
59
60
  * The path of the tree's top file row once every folder on the way is open — the first file in
60
61
  * display order (files before folders, so a root file wins over anything nested); `null` for no files.
@@ -109,36 +110,38 @@ export function packageFileTreeKeyAction({ rows, currentPath, key, isExpanded },
109
110
  return {};
110
111
  const index = Math.max(0, rows.findIndex((row) => row.node.path === currentPath));
111
112
  const row = rows[index];
112
- const node = row.node;
113
- const open = node.kind === "folder" && isExpanded({ path: node.path });
114
- switch (key) {
115
- case "ArrowDown":
116
- return { focus: rows[Math.min(index + 1, rows.length - 1)].node.path };
117
- case "ArrowUp":
118
- return { focus: rows[Math.max(index - 1, 0)].node.path };
119
- case "Home":
120
- return { focus: rows[0].node.path };
121
- case "End":
122
- return { focus: rows[rows.length - 1].node.path };
123
- case "ArrowRight":
124
- if (node.kind !== "folder")
125
- return { focus: node.path };
126
- if (!open)
127
- return { focus: node.path, setExpanded: { path: node.path, expanded: true } };
128
- return { focus: rows[index + 1]?.parentPath === node.path ? rows[index + 1].node.path : node.path };
129
- case "ArrowLeft":
130
- if (open)
131
- return { focus: node.path, setExpanded: { path: node.path, expanded: false } };
132
- return { focus: row.parentPath ?? node.path };
133
- case "Enter":
134
- case " ":
135
- if (node.kind === "file")
136
- return { focus: node.path, open: node.path };
137
- return { focus: node.path, setExpanded: { path: node.path, expanded: !open } };
138
- default:
139
- return {};
140
- }
113
+ const open = row.node.kind === "folder" && isExpanded({ path: row.node.path });
114
+ // A Map, not an object literal, so a key like "constructor" can never resolve to a prototype member.
115
+ return KEY_HANDLERS.get(key)?.({ rows, index, row, open }) ?? {};
116
+ }
117
+ const focusAt = (rows, index) => ({ focus: rows[index].node.path });
118
+ function arrowRight({ rows, index, row: { node }, open }, _optional = {}) {
119
+ if (node.kind !== "folder")
120
+ return { focus: node.path };
121
+ if (!open)
122
+ return { focus: node.path, setExpanded: { path: node.path, expanded: true } };
123
+ return { focus: rows[index + 1]?.parentPath === node.path ? rows[index + 1].node.path : node.path };
124
+ }
125
+ function arrowLeft({ row, open }, _optional = {}) {
126
+ if (open)
127
+ return { focus: row.node.path, setExpanded: { path: row.node.path, expanded: false } };
128
+ return { focus: row.parentPath ?? row.node.path };
129
+ }
130
+ function activate({ row: { node }, open }, _optional = {}) {
131
+ if (node.kind === "file")
132
+ return { focus: node.path, open: node.path };
133
+ return { focus: node.path, setExpanded: { path: node.path, expanded: !open } };
141
134
  }
135
+ const KEY_HANDLERS = new Map([
136
+ ["ArrowDown", ({ rows, index }) => focusAt(rows, Math.min(index + 1, rows.length - 1))],
137
+ ["ArrowUp", ({ rows, index }) => focusAt(rows, Math.max(index - 1, 0))],
138
+ ["Home", ({ rows }) => focusAt(rows, 0)],
139
+ ["End", ({ rows }) => focusAt(rows, rows.length - 1)],
140
+ ["ArrowRight", arrowRight],
141
+ ["ArrowLeft", arrowLeft],
142
+ ["Enter", activate],
143
+ [" ", activate],
144
+ ]);
142
145
  const ICON_KIND_BY_EXTENSION = {
143
146
  json: "json",
144
147
  jsonc: "json",
@@ -1,5 +1,5 @@
1
1
  import type { AgentPluginRowView } from '../hooks/AgentPluginPanel.hooks.js';
2
2
  export declare function AgentPluginRow({ row }: {
3
3
  row: AgentPluginRowView;
4
- }, _optional?: {}): import("react").JSX.Element;
4
+ }): import("react").JSX.Element;
5
5
  //# sourceMappingURL=AgentPluginRow.d.ts.map
@@ -1,8 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Badge, Button, IconButton, Switch } from '@jini-ai/ui-kit/react';
3
3
  import { useAgentPluginRow } from '../hooks/AgentPluginRow.hooks.js';
4
- export function AgentPluginRow({ row }, _optional = {}) {
4
+ export function AgentPluginRow({ row }) {
5
5
  const vm = useAgentPluginRow({ row });
6
- return _jsxs("li", { "data-jini-part": "agent-plugins.row", children: [_jsx("span", { "aria-hidden": true, children: vm.glyph }), _jsxs("h3", { children: [vm.name, " ", vm.version && _jsx("small", { children: vm.version })] }), vm.description && _jsx("p", { children: vm.description }), _jsx(Badge, { children: vm.stateLabel }), vm.installed ? _jsx(Switch, { label: vm.toggleLabel, checked: vm.enabled, disabled: !vm.canWrite || vm.busy, onCheckedChange: vm.toggle }) : _jsx(Button, { attrs: vm.actionAttrs, disabled: !vm.canWrite || vm.busy, pending: vm.busy, onPress: vm.act, children: vm.actionText }), _jsx(Button, { attrs: vm.expandAttrs, onPress: vm.expand, children: "Details" }), _jsx(IconButton, { label: vm.inspectLabel, attrs: vm.inspectAttrs, onPress: vm.inspect, children: "Inspect" }), _jsx(IconButton, { label: vm.uninstallLabel, attrs: vm.uninstallAttrs, disabled: true, children: "Uninstall" }), _jsxs("div", { id: vm.detailId, hidden: vm.detailHidden, children: [vm.description && _jsx("p", { children: vm.description }), vm.hasSkills && _jsxs("section", { "aria-label": "Portable components", children: [_jsx("h4", { children: "Portable components" }), _jsx("ul", { children: vm.skills.map(skill => _jsxs("li", { children: [_jsx("strong", { children: skill.name }), _jsx("p", { children: skill.summary })] }, skill.name)) })] }), vm.hasServers && _jsxs("section", { "aria-label": "MCP servers", children: [_jsx("h4", { children: "MCP servers" }), _jsx("ul", { children: vm.servers.map(id => _jsx("li", { children: id }, id)) })] }), vm.hasKeywords && _jsxs("section", { "aria-label": "Keywords", children: [_jsx("h4", { children: "Keywords" }), _jsx("ul", { children: vm.keywords.map(word => _jsx("li", { children: word }, word)) })] })] })] });
6
+ return _jsxs("li", { "data-jini-part": "agent-plugins.row", children: [_jsx("span", { "aria-hidden": true, children: vm.glyph }), _jsxs("h3", { children: [vm.name, " ", vm.version && _jsx("small", { children: vm.version })] }), vm.description && _jsx("p", { children: vm.description }), _jsx(Badge, { children: vm.stateLabel }), _jsx(AgentPluginRowActivation, { vm: vm }), _jsx(Button, { attrs: vm.expandAttrs, onPress: vm.expand, children: "Details" }), _jsx(IconButton, { label: vm.inspectLabel, attrs: vm.inspectAttrs, onPress: vm.inspect, children: "Inspect" }), _jsx(IconButton, { label: vm.uninstallLabel, attrs: vm.uninstallAttrs, disabled: true, children: "Uninstall" }), _jsx(AgentPluginRowDetails, { vm: vm })] });
7
+ }
8
+ function AgentPluginRowActivation({ vm }) {
9
+ const disabled = !vm.canWrite || vm.busy;
10
+ return vm.installed ? _jsx(Switch, { label: vm.toggleLabel, checked: vm.enabled, disabled: disabled, onCheckedChange: vm.toggle }) : _jsx(Button, { attrs: vm.actionAttrs, disabled: disabled, pending: vm.busy, onPress: vm.act, children: vm.actionText });
11
+ }
12
+ function AgentPluginRowDetails({ vm }) {
13
+ return _jsxs("div", { id: vm.detailId, hidden: vm.detailHidden, children: [vm.description && _jsx("p", { children: vm.description }), vm.hasSkills && _jsxs("section", { "aria-label": "Portable components", children: [_jsx("h4", { children: "Portable components" }), _jsx("ul", { children: vm.skills.map(skill => _jsxs("li", { children: [_jsx("strong", { children: skill.name }), _jsx("p", { children: skill.summary })] }, skill.name)) })] }), vm.hasServers && _jsxs("section", { "aria-label": "MCP servers", children: [_jsx("h4", { children: "MCP servers" }), _jsx("ul", { children: vm.servers.map(id => _jsx("li", { children: id }, id)) })] }), vm.hasKeywords && _jsxs("section", { "aria-label": "Keywords", children: [_jsx("h4", { children: "Keywords" }), _jsx("ul", { children: vm.keywords.map(word => _jsx("li", { children: word }, word)) })] })] });
7
14
  }
8
15
  //# sourceMappingURL=AgentPluginRow.js.map
@@ -7,8 +7,9 @@ export interface AgentPluginInspectorProps {
7
7
  readonly onClose: (required: Record<string, never>, optional?: Record<string, never>) => void;
8
8
  }
9
9
  export declare function useAgentPluginInspector({ pluginId, permissions, name }: AgentPluginInspectorProps, _optional?: {}): {
10
- title: string;
11
- denied: boolean;
10
+ select({ path }: {
11
+ path: string;
12
+ }, _optional?: {}): void;
12
13
  files: readonly AgentPluginPackageFile[];
13
14
  selected: AgentPluginPackageFile | null;
14
15
  selectedPath: string | null;
@@ -23,9 +24,8 @@ export declare function useAgentPluginInspector({ pluginId, permissions, name }:
23
24
  role: "status";
24
25
  } | null;
25
26
  listNotice: "Some files are not listed: this package is larger than the viewer’s limits." | null;
26
- select({ path }: {
27
- path: string;
28
- }, _optional?: {}): void;
27
+ title: string;
28
+ denied: boolean;
29
29
  };
30
30
  export declare function useAgentPluginFileTree({ files, selectedPath, onOpen }: {
31
31
  files: readonly AgentPluginPackageFile[];
@@ -9,14 +9,23 @@ export function useAgentPluginInspector({ pluginId, permissions, name }, _option
9
9
  const { agentPluginsApi } = useAgentPluginsPorts({}), key = permissions.join('\0');
10
10
  const { controller, snapshot } = useController({ create: () => createAgentPluginFilesController({ api: agentPluginsApi, permissions }), dependencies: [agentPluginsApi, key, pluginId] }, { start: ({ controller }) => { void controller.open({ pluginId }); } });
11
11
  // Do not show any stored read tagged with another id, even before the new effect commits.
12
- const state = snapshot?.pluginId === pluginId ? snapshot : null, files = state?.listing?.files ?? [];
13
- const selected = selectAgentPluginFile({ files, path: state?.selectedPath ?? '' });
14
- return { title: `${name} — Package files`, denied: !permissions.includes(AGENT_PLUGINS_READ), files, selected, selectedPath: selected?.relativePath ?? null,
15
- status: state?.error ? { text: state.error, role: 'alert' } : !state || state.loading ? { text: m.filesLoading, role: 'status' } : !files.length ? { text: m.filesEmpty, role: 'status' } : null,
16
- listNotice: state?.listing?.truncated ? m.filesTruncated : null,
12
+ const state = snapshot?.pluginId === pluginId ? snapshot : null;
13
+ return { title: `${name} — Package files`, denied: !permissions.includes(AGENT_PLUGINS_READ), ...inspectorFilesView({ state }),
17
14
  select({ path }, _optional = {}) { controller?.select({ path }); },
18
15
  };
19
16
  }
17
+ const filesOf = (state) => state?.listing?.files ?? [];
18
+ function inspectorFilesView({ state }, _optional = {}) {
19
+ const files = filesOf(state), selected = selectAgentPluginFile({ files, path: state?.selectedPath ?? '' });
20
+ return { files, selected, selectedPath: selected?.relativePath ?? null, status: inspectorStatus({ state, hasFiles: files.length > 0 }), listNotice: state?.listing?.truncated ? m.filesTruncated : null };
21
+ }
22
+ function inspectorStatus({ state, hasFiles }, _optional = {}) {
23
+ if (state?.error)
24
+ return { text: state.error, role: 'alert' };
25
+ if (!state || state.loading)
26
+ return { text: m.filesLoading, role: 'status' };
27
+ return hasFiles ? null : { text: m.filesEmpty, role: 'status' };
28
+ }
20
29
  const fileGlyphs = { json: '{}', markdown: 'M↓', javascript: 'JS', typescript: 'TS', config: '⚙', html: '<>', css: '#', image: '▧', shell: '>', text: '¶', file: '▤' };
21
30
  export function useAgentPluginFileTree({ files, selectedPath, onOpen }, _optional = {}) {
22
31
  const tree = buildPackageFileTree({ paths: files.map(f => f.relativePath) }), [toggled, setToggled] = useState(new Map());
@@ -1,10 +1,21 @@
1
1
  import type { TabViewProps } from '../../../core/react/bind-react.js';
2
+ type PanelMode = 'installed' | 'downloaded';
2
3
  export declare function useAgentPluginPanel({ props, mode }: {
3
4
  props: TabViewProps;
4
- mode: 'installed' | 'downloaded';
5
+ mode: PanelMode;
5
6
  }, _optional?: {}): {
7
+ uninstallNoteId: string;
8
+ specUrl: string;
9
+ reload(_required: Record<string, never>, _optional?: {}): void;
10
+ lede: "Every plugin installed in this workspace. Only switched-on plugins reach the assistant’s prompt." | "An installed plugin sits inert until you enable it. Enabling one puts its skills in the assistant’s prompt for every run.";
11
+ label: string;
12
+ uninstallNotice: string;
13
+ loading: boolean;
14
+ error: string | null | undefined;
15
+ actionError: string | null | undefined;
16
+ empty: boolean;
6
17
  denied: boolean;
7
- mode: "installed" | "downloaded";
18
+ mode: PanelMode;
8
19
  rows: {
9
20
  id: string;
10
21
  name: string;
@@ -15,7 +26,7 @@ export declare function useAgentPluginPanel({ props, mode }: {
15
26
  stateLabel: string;
16
27
  busy: boolean;
17
28
  expanded: boolean;
18
- mode: "installed" | "downloaded";
29
+ mode: PanelMode;
19
30
  canWrite: boolean;
20
31
  toggleLabel: string;
21
32
  actionLabel: string;
@@ -52,16 +63,7 @@ export declare function useAgentPluginPanel({ props, mode }: {
52
63
  inspectLabel: string;
53
64
  uninstallLabel: string;
54
65
  }[];
55
- loading: boolean;
56
- error: string | null | undefined;
57
- actionError: string | null | undefined;
58
- empty: boolean;
59
- lede: "Every plugin installed in this workspace. Only switched-on plugins reach the assistant’s prompt." | "An installed plugin sits inert until you enable it. Enabling one puts its skills in the assistant’s prompt for every run.";
60
- label: string;
61
- uninstallNotice: string;
62
- uninstallNoteId: string;
63
- specUrl: string;
64
- reload(_required: Record<string, never>, _optional?: {}): void;
65
66
  };
66
67
  export type AgentPluginRowView = ReturnType<typeof useAgentPluginPanel>['rows'][number];
68
+ export {};
67
69
  //# sourceMappingURL=AgentPluginPanel.hooks.d.ts.map
@@ -4,48 +4,62 @@ import { useAgentPluginsPageScope } from './AgentPluginsPage.hooks.js';
4
4
  const glyphs = { compliance: '◈', deploy: '↗', design: '◇', integration: '⇄', content: '▤', package: '▣' };
5
5
  export function useAgentPluginPanel({ props, mode }, _optional = {}) {
6
6
  const { controller, snapshot: state, permissions, uninstallNoteId } = useAgentPluginsPageScope({});
7
- const canRead = permissions.includes(AGENT_PLUGINS_READ) && !!props.permissions?.includes(AGENT_PLUGINS_READ);
8
- const canWrite = canRead && permissions.includes(AGENT_PLUGINS_WRITE) && !!props.permissions?.includes(AGENT_PLUGINS_WRITE);
9
- // Scope is intentionally unfiltered on BOTH tabs, including switched-off bundled packages.
10
- const busyIds = state?.busyIds ?? [], expandedIds = state?.expandedIds ?? [];
11
- const rows = (canRead ? state?.plugins ?? [] : []).map(plugin => {
12
- const name = humanizeAgentPluginId({ pluginId: plugin.pluginId }), busy = busyIds.includes(plugin.pluginId);
13
- const expanded = expandedIds.includes(plugin.pluginId);
14
- // Derived from the id alone, so handles survive sorting and switching tabs.
15
- const handle = `agent-plugin-${encodeURIComponent(plugin.pluginId)}`;
16
- const turnOff = () => controller?.requestDisable({ pluginId: plugin.pluginId, variant: mode === 'installed' ? 'disable' : 'remove' });
17
- return {
18
- id: plugin.pluginId, name, version: plugin.version, description: plugin.description,
19
- glyph: glyphs[agentPluginGlyphKind({ plugin })], enabled: plugin.enabled, stateLabel: plugin.enabled ? 'Enabled' : 'Disabled',
20
- busy, expanded, mode, canWrite, toggleLabel: `${plugin.enabled ? 'Disable' : 'Enable'} ${name}`,
21
- actionLabel: `${plugin.enabled ? 'Turn off' : 'Enable'} ${name}`, actionText: plugin.enabled ? 'Turn off' : 'Enable',
22
- skills: plugin.skills, keywords: plugin.keywords, servers: plugin.mcpServerIds,
23
- hasSkills: plugin.skills.length > 0, hasKeywords: plugin.keywords.length > 0, hasServers: plugin.mcpServerIds.length > 0,
24
- toggle(_required, _optional = {}) { if (!canWrite)
25
- return; if (plugin.enabled)
26
- turnOff();
27
- else
28
- void controller?.setEnabled({ pluginId: plugin.pluginId, enabled: true }); },
29
- act(_required, _optional = {}) { if (!canWrite)
30
- return; if (plugin.enabled)
31
- turnOff();
32
- else
33
- void controller?.setEnabled({ pluginId: plugin.pluginId, enabled: true }); },
34
- expand(_required, _optional = {}) { controller?.toggleExpanded({ pluginId: plugin.pluginId }); },
35
- inspect(_required, _optional = {}) { controller?.inspect({ pluginId: plugin.pluginId }); },
36
- expandAttrs: { 'aria-label': `${expanded ? 'Collapse' : 'Expand'} ${name}`, 'aria-expanded': expanded, 'data-agent-handle': `${handle}-expand` },
37
- actionAttrs: { 'aria-label': `${plugin.enabled ? 'Turn off' : 'Enable'} ${name}`, 'data-agent-handle': `${handle}-activation` },
38
- inspectAttrs: { 'data-agent-handle': `${handle}-inspect` },
39
- uninstallAttrs: { 'aria-describedby': uninstallNoteId },
40
- inspectLabel: `Inspect ${name} package files`, uninstallLabel: `Uninstall ${name} — unavailable`,
41
- };
42
- });
43
- return { denied: !canRead, mode, rows, loading: !state || (!state.plugins && !state.error), error: state?.error, actionError: state?.actionError, empty: !!state && state.plugins !== null && rows.length === 0,
44
- lede: mode === 'installed' ? m.installedLede : m.downloadedLede,
45
- label: mode === 'installed' ? 'Installed Agent Plugins' : 'Downloaded Agent Plugins',
46
- uninstallNotice: typeof props.params.uninstallNotice === 'string' ? props.params.uninstallNotice : m.uninstallUnavailable,
7
+ const { canRead, canWrite } = panelGrants({ permissions, granted: props.permissions ?? [] });
8
+ const rows = panelRows({ state, canRead, controller, mode, canWrite, uninstallNoteId });
9
+ return { denied: !canRead, mode, rows, ...panelStatus({ state, rowCount: rows.length }),
10
+ ...panelCopy({ mode, params: props.params }),
47
11
  uninstallNoteId, specUrl: AGENT_PLUGINS_SPEC_URL,
48
12
  reload(_required, _optional = {}) { void controller?.load({}); },
49
13
  };
50
14
  }
15
+ /** Both the page scope and the tab's own grants must allow an action. */
16
+ function panelGrants({ permissions, granted }, _optional = {}) {
17
+ const both = (permission) => permissions.includes(permission) && granted.includes(permission);
18
+ const canRead = both(AGENT_PLUGINS_READ);
19
+ return { canRead, canWrite: canRead && both(AGENT_PLUGINS_WRITE) };
20
+ }
21
+ function panelStatus({ state, rowCount }, _optional = {}) {
22
+ return { loading: !state || (!state.plugins && !state.error), error: state?.error, actionError: state?.actionError, empty: !!state && state.plugins !== null && rowCount === 0 };
23
+ }
24
+ function panelCopy({ mode, params }, _optional = {}) {
25
+ const installed = mode === 'installed';
26
+ return { lede: installed ? m.installedLede : m.downloadedLede,
27
+ label: installed ? 'Installed Agent Plugins' : 'Downloaded Agent Plugins',
28
+ uninstallNotice: typeof params.uninstallNotice === 'string' ? params.uninstallNotice : m.uninstallUnavailable };
29
+ }
30
+ function panelRows({ state, canRead, ...context }, _optional = {}) {
31
+ // Scope is intentionally unfiltered on BOTH tabs, including switched-off bundled packages.
32
+ const busyIds = state?.busyIds ?? [], expandedIds = state?.expandedIds ?? [];
33
+ return (canRead ? state?.plugins ?? [] : []).map(plugin => agentPluginRowView({ ...context, plugin, busy: busyIds.includes(plugin.pluginId), expanded: expandedIds.includes(plugin.pluginId) }));
34
+ }
35
+ function agentPluginRowView({ plugin, busy, expanded, controller, mode, canWrite, uninstallNoteId }, _optional = {}) {
36
+ const name = humanizeAgentPluginId({ pluginId: plugin.pluginId });
37
+ // Derived from the id alone, so handles survive sorting and switching tabs.
38
+ const handle = `agent-plugin-${encodeURIComponent(plugin.pluginId)}`;
39
+ const turnOff = () => controller?.requestDisable({ pluginId: plugin.pluginId, variant: mode === 'installed' ? 'disable' : 'remove' });
40
+ // The switch and the button do the same thing: turning off asks first, enabling writes directly.
41
+ const activate = () => { if (!canWrite)
42
+ return; if (plugin.enabled)
43
+ turnOff();
44
+ else
45
+ void controller?.setEnabled({ pluginId: plugin.pluginId, enabled: true }); };
46
+ const verb = plugin.enabled ? 'Turn off' : 'Enable';
47
+ return {
48
+ id: plugin.pluginId, name, version: plugin.version, description: plugin.description,
49
+ glyph: glyphs[agentPluginGlyphKind({ plugin })], enabled: plugin.enabled, stateLabel: plugin.enabled ? 'Enabled' : 'Disabled',
50
+ busy, expanded, mode, canWrite, toggleLabel: `${plugin.enabled ? 'Disable' : 'Enable'} ${name}`,
51
+ actionLabel: `${verb} ${name}`, actionText: verb,
52
+ skills: plugin.skills, keywords: plugin.keywords, servers: plugin.mcpServerIds,
53
+ hasSkills: plugin.skills.length > 0, hasKeywords: plugin.keywords.length > 0, hasServers: plugin.mcpServerIds.length > 0,
54
+ toggle(_required, _optional = {}) { activate(); },
55
+ act(_required, _optional = {}) { activate(); },
56
+ expand(_required, _optional = {}) { controller?.toggleExpanded({ pluginId: plugin.pluginId }); },
57
+ inspect(_required, _optional = {}) { controller?.inspect({ pluginId: plugin.pluginId }); },
58
+ expandAttrs: { 'aria-label': `${expanded ? 'Collapse' : 'Expand'} ${name}`, 'aria-expanded': expanded, 'data-agent-handle': `${handle}-expand` },
59
+ actionAttrs: { 'aria-label': `${verb} ${name}`, 'data-agent-handle': `${handle}-activation` },
60
+ inspectAttrs: { 'data-agent-handle': `${handle}-inspect` },
61
+ uninstallAttrs: { 'aria-describedby': uninstallNoteId },
62
+ inspectLabel: `Inspect ${name} package files`, uninstallLabel: `Uninstall ${name} — unavailable`,
63
+ };
64
+ }
51
65
  //# sourceMappingURL=AgentPluginPanel.hooks.js.map
@@ -10,6 +10,19 @@ export interface AgentPluginsPageScope {
10
10
  export declare const AgentPluginsPageContext: import("react").Context<AgentPluginsPageScope | null>;
11
11
  export declare function useAgentPluginsPageScope(_required: Record<string, never>, _optional?: {}): AgentPluginsPageScope;
12
12
  export declare function useAgentPluginsPage(props: ModulePageProps, _optional?: {}): {
13
+ closeInspector(_required: Record<string, never>, _optional?: {}): void;
14
+ confirm(_required: Record<string, never>, _optional?: {}): Promise<void>;
15
+ cancel(_required: Record<string, never>, _optional?: {}): void;
16
+ inspector: {
17
+ pluginId: string;
18
+ name: string;
19
+ } | null;
20
+ confirmation: {
21
+ title: string;
22
+ confirmLabel: string;
23
+ body: string;
24
+ } | null;
25
+ confirming: boolean;
13
26
  denied: boolean;
14
27
  scope: {
15
28
  controller: {
@@ -47,18 +60,5 @@ export declare function useAgentPluginsPage(props: ModulePageProps, _optional?:
47
60
  onValueChange({ value }: {
48
61
  value: string;
49
62
  }, _optional?: {}): void;
50
- inspector: {
51
- pluginId: string;
52
- name: string;
53
- } | null;
54
- closeInspector(_required: Record<string, never>, _optional?: {}): void;
55
- confirmation: {
56
- title: string;
57
- confirmLabel: string;
58
- body: string;
59
- } | null;
60
- confirming: boolean;
61
- confirm(_required: Record<string, never>, _optional?: {}): Promise<void>;
62
- cancel(_required: Record<string, never>, _optional?: {}): void;
63
63
  };
64
64
  //# sourceMappingURL=AgentPluginsPage.hooks.d.ts.map
@@ -16,24 +16,36 @@ export function useAgentPluginsPage(props, _optional = {}) {
16
16
  const permissions = props.description.visible ? props.description.grants : [], key = permissions.join('\0');
17
17
  const { controller, snapshot } = useController({ create: () => createAgentPluginsController({ api: agentPluginsApi, permissions }), dependencies: [agentPluginsApi, key] }, { start: ({ controller }) => { void controller.load({}); } });
18
18
  const [localTab, setLocalTab] = useState('installed'), uninstallNoteId = useId();
19
- const visible = props.description.visible ? props.description.tabs.filter(t => t.visible) : [];
20
- const active = visible.find(t => t.id.endsWith(`.${props.requestedTab ?? localTab}`)) ?? visible[0];
21
- const activeId = active?.id.slice(active.id.lastIndexOf('.') + 1) ?? 'installed';
22
- const pending = snapshot?.pendingDisable, confirming = !!pending && !!snapshot?.busyIds.includes(pending.pluginId);
23
- const confirmation = pending ? agentPluginDisableCopy(pending) : null;
24
- const inspected = snapshot?.plugins?.find(p => p.pluginId === snapshot.inspectedId);
19
+ const { visible, active, activeId } = pageTabs({ description: props.description, tab: props.requestedTab ?? localTab });
25
20
  return {
26
21
  denied: !props.description.visible || !permissions.includes(AGENT_PLUGINS_READ),
27
22
  scope: { controller, snapshot, permissions, uninstallNoteId },
28
23
  activeId, ActiveTab: active ? props.tabs[activeId] : undefined, params: active?.params ?? {}, permissions,
29
- items: visible.map(t => ({ id: t.id.slice(t.id.lastIndexOf('.') + 1), label: t.label, content: null })),
24
+ items: visible.map(t => ({ id: tabSuffix(t.id), label: t.label, content: null })),
30
25
  onValueChange({ value }, _optional = {}) { setLocalTab(value); props.onTabChange?.({ tab: value }); },
31
- inspector: inspected ? { pluginId: inspected.pluginId, name: humanizeAgentPluginId({ pluginId: inspected.pluginId }) } : null,
26
+ ...pageDialogs({ snapshot }),
32
27
  closeInspector(_required, _optional = {}) { controller?.closeInspector({}); },
33
- confirmation, confirming,
34
- async confirm(_required, _optional = {}) { if (!await controller?.confirmDisable({}))
35
- throw new Error('Activation was not updated'); },
28
+ async confirm(_required, _optional = {}) {
29
+ const pluginId = snapshot?.pendingDisable?.pluginId ?? '(none pending)';
30
+ if (!await controller?.confirmDisable({}))
31
+ throw new Error(`Activation was not updated for agent plugin ${pluginId}: the disable was refused or failed; retry, or reload the list if it changed`);
32
+ },
36
33
  cancel(_required, _optional = {}) { controller?.cancelDisable({}); },
37
34
  };
38
35
  }
36
+ /** A tab id is namespaced (`agentPlugins.installed`); the page addresses tabs by the last segment. */
37
+ const tabSuffix = (id) => id.slice(id.lastIndexOf('.') + 1);
38
+ function pageTabs({ description, tab }, _optional = {}) {
39
+ const visible = description.visible ? description.tabs.filter(t => t.visible) : [];
40
+ const active = visible.find(t => t.id.endsWith(`.${tab}`)) ?? visible[0];
41
+ return { visible, active, activeId: active ? tabSuffix(active.id) : 'installed' };
42
+ }
43
+ function pageDialogs({ snapshot }, _optional = {}) {
44
+ const pending = snapshot?.pendingDisable, confirming = !!pending && !!snapshot?.busyIds.includes(pending.pluginId);
45
+ const inspected = snapshot?.plugins?.find(p => p.pluginId === snapshot.inspectedId);
46
+ return {
47
+ inspector: inspected ? { pluginId: inspected.pluginId, name: humanizeAgentPluginId({ pluginId: inspected.pluginId }) } : null,
48
+ confirmation: pending ? agentPluginDisableCopy(pending) : null, confirming,
49
+ };
50
+ }
39
51
  //# sourceMappingURL=AgentPluginsPage.hooks.js.map