@jini-ai/admin 0.4.12 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -2
- package/dist/agent-plugins/controllers/agent-plugin-files.controller.js +14 -9
- package/dist/agent-plugins/controllers/agent-plugins.controller.js +12 -9
- package/dist/agent-plugins/package-file-tree.js +54 -51
- package/dist/agent-plugins/react/components/AgentPluginRow.d.ts +1 -1
- package/dist/agent-plugins/react/components/AgentPluginRow.js +9 -2
- package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.d.ts +5 -5
- package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.js +14 -5
- package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.d.ts +15 -13
- package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.js +54 -40
- package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.d.ts +13 -13
- package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.js +23 -11
- package/dist/browser/navigation.js +44 -27
- package/dist/comments/adapters/memory.js +30 -12
- package/dist/comments/conformance/comments-api.conformance.js +55 -33
- package/dist/comments/controllers/comment-queue.controller.js +13 -4
- package/dist/comments/react/hooks/use-comment-queue.hooks.d.ts +17 -2
- package/dist/comments/react/hooks/use-comment-queue.hooks.js +79 -30
- package/dist/comments/react/hooks/use-comment-settings.hooks.js +9 -5
- package/dist/comments/react/index.d.ts +2 -1
- package/dist/comments/react/index.js +1 -1
- package/dist/consent/react/ConsentFields.d.ts +35 -0
- package/dist/consent/react/ConsentFields.js +76 -0
- package/dist/consent/react/ConsentPreviewFrame.d.ts +14 -0
- package/dist/consent/react/ConsentPreviewFrame.js +14 -0
- package/dist/consent/react/ConsentSettingsPanel.d.ts +27 -4
- package/dist/consent/react/ConsentSettingsPanel.js +6 -17
- package/dist/consent/react/index.d.ts +6 -1
- package/dist/consent/react/index.js +2 -0
- package/dist/consent/react/settings-panel.hooks.d.ts +28 -1
- package/dist/consent/react/settings-panel.hooks.js +26 -0
- package/dist/core/entities/rules.js +126 -81
- package/dist/core/manifest/rules.js +10 -10
- package/dist/core/module/create-admin.js +195 -117
- package/dist/core/ports/redirects.d.ts +8 -6
- package/dist/core/ports/redirects.js +8 -6
- package/dist/core/routing/rules.js +9 -4
- package/dist/core/transport/http.js +37 -20
- package/dist/forms/adapters/memory.js +22 -11
- package/dist/forms/conformance/forms-api.conformance.d.ts +6 -4
- package/dist/forms/conformance/forms-api.conformance.js +42 -46
- package/dist/forms/field-settings.js +150 -115
- package/dist/forms/html-rules.js +45 -28
- package/dist/forms/react/components/FieldSettingsRows.d.ts +22 -0
- package/dist/forms/react/components/FieldSettingsRows.js +29 -0
- package/dist/forms/react/components/FieldSettingsSection.d.ts +1 -1
- package/dist/forms/react/components/FieldSettingsSection.js +5 -6
- package/dist/forms/react/components/FormEditorFieldsBody.d.ts +1 -1
- package/dist/forms/react/components/FormEditorFieldsBody.js +4 -15
- package/dist/forms/react/components/FormEditorFieldsBodyParts.d.ts +18 -0
- package/dist/forms/react/components/FormEditorFieldsBodyParts.js +32 -0
- package/dist/forms/react/components/FormSubmissions.d.ts +1 -1
- package/dist/forms/react/components/FormSubmissions.js +4 -20
- package/dist/forms/react/components/FormSubmissionsTable.d.ts +19 -0
- package/dist/forms/react/components/FormSubmissionsTable.js +22 -0
- package/dist/media/adapters/memory.js +31 -26
- package/dist/media/controllers/edit-media.controller.js +2 -1
- package/dist/media/controllers/library.controller.js +48 -35
- package/dist/media/controllers/providers.controller.js +2 -1
- package/dist/media/html-attributes.js +29 -15
- package/dist/media/index.d.ts +1 -1
- package/dist/media/index.js +1 -1
- package/dist/media/react/components/MediaIcons.d.ts +0 -3
- package/dist/media/react/components/MediaIcons.js +0 -9
- package/dist/media/react/components/MediaLightbox.d.ts +2 -1
- package/dist/media/react/components/MediaLightbox.js +11 -5
- package/dist/media/react/hooks/LibraryTab.hooks.d.ts +55 -45
- package/dist/media/react/hooks/LibraryTab.hooks.js +107 -70
- package/dist/media/react/hooks/MediaCard.hooks.d.ts +27 -31
- package/dist/media/react/hooks/MediaCard.hooks.js +52 -26
- package/dist/media/react/hooks/MediaGrid.hooks.d.ts +1 -1
- package/dist/media/react/hooks/MediaLightbox.hooks.d.ts +10 -14
- package/dist/media/react/hooks/MediaLightbox.hooks.js +9 -56
- package/dist/media/react/hooks/MediaPage.hooks.d.ts +6 -0
- package/dist/media/react/hooks/MediaPage.hooks.js +29 -12
- package/dist/media/react/hooks/ProvidersTab.hooks.d.ts +33 -33
- package/dist/media/react/hooks/ProvidersTab.hooks.js +119 -79
- package/dist/media/react/hooks/UploadButton.hooks.d.ts +7 -5
- package/dist/media/react/hooks/UploadButton.hooks.js +36 -24
- package/dist/media/react/index.js +4 -0
- package/dist/media/react/tabs/LibraryTab.d.ts +1 -1
- package/dist/media/react/tabs/LibraryTab.js +2 -2
- package/dist/media/rules.d.ts +7 -0
- package/dist/media/rules.js +25 -2
- package/dist/react/components/ConfirmDialog/ConfirmDialog.js +5 -8
- package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.d.ts +20 -0
- package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.js +24 -0
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +16 -7
- package/dist/react/components/RowMenu/RowMenu.js +16 -10
- package/dist/react/components/Sidebar.js +32 -26
- package/dist/react/entities/EntityEdit.js +17 -14
- package/dist/react/entities/EntityField.d.ts +5 -2
- package/dist/react/entities/EntityField.js +38 -23
- package/dist/react/entities/EntityList.js +6 -5
- package/dist/react/entities/RelationTruncationNotice.d.ts +9 -0
- package/dist/react/entities/RelationTruncationNotice.js +9 -0
- package/dist/react/entities/use-entity-edit.d.ts +4 -2
- package/dist/react/entities/use-entity-edit.js +52 -33
- package/dist/react/shell/AdminShell.js +40 -23
- package/dist/react/shell/model.d.ts +4 -1
- package/dist/react/shell/model.js +3 -2
- package/dist/react/shell/use-admin-shell-session.js +25 -26
- package/dist/redirects/adapters/memory.js +9 -2
- package/dist/redirects/conformance/redirects-api.conformance.js +66 -36
- package/dist/redirects/react/hooks/use-import-redirects-form.hooks.d.ts +1 -1
- package/dist/redirects/react/hooks/use-import-redirects-form.hooks.js +10 -3
- package/dist/redirects/react/hooks/wired.hooks.d.ts +1 -1
- package/dist/security/adapters/http.js +25 -10
- package/dist/security/adapters/memory.js +19 -5
- package/dist/security/controllers/access-tokens.controller.js +27 -26
- package/dist/security/controllers/other-credentials.controller.js +4 -2
- package/dist/security/controllers/root-key.controller.js +17 -9
- package/dist/security/react/hooks/AccessTokensTab.hooks.d.ts +40 -38
- package/dist/security/react/hooks/AccessTokensTab.hooks.js +36 -9
- package/dist/security/react/hooks/RootKeyTab.hooks.d.ts +14 -14
- package/dist/security/react/hooks/RootKeyTab.hooks.js +12 -3
- package/dist/security/react/tabs/RootKeyTab.d.ts +1 -1
- package/dist/security/react/tabs/RootKeyTab.js +12 -3
- package/dist/security/rules.js +45 -25
- package/dist/seo/controllers/seo.controller.js +4 -2
- package/dist/seo/react/components/MediaRefField.d.ts +1 -1
- package/dist/seo/react/components/MediaRefField.js +19 -2
- package/dist/seo/react/hooks/MediaRefField.hooks.js +3 -1
- package/dist/seo/react/hooks/SeoPage.hooks.d.ts +1 -1
- package/dist/seo/react/hooks/use-share-image-warning.hooks.js +48 -34
- package/dist/seo/react/pages/SeoPage.d.ts +4 -4
- package/dist/seo/react/pages/SeoPage.js +26 -6
- package/dist/seo/rules.js +39 -10
- package/dist/source-control/conformance/source-control-api.conformance.js +68 -38
- package/dist/source-control/controllers/source-control.controller.js +20 -8
- package/dist/source-control/react/hooks/ProvidersTab.hooks.d.ts +2 -1
- package/dist/source-control/react/hooks/ProvidersTab.hooks.js +18 -11
- package/dist/widgets/conformance/widgets-api.conformance.js +15 -1
- package/dist/widgets/controllers/instance-editor.controller.js +33 -24
- package/dist/widgets/controllers/library.controller.js +18 -12
- package/dist/widgets/controllers/region-editor.controller.js +6 -5
- package/dist/widgets/react/hooks/page-views.hooks.d.ts +1 -0
- package/dist/widgets/react/hooks/page-views.hooks.js +3 -2
- package/dist/widgets/react/hooks/use-widget-regions.hooks.d.ts +1 -1
- package/dist/widgets/react/hooks/use-widget-regions.hooks.js +9 -3
- package/dist/widgets/react/hooks/use-widgets-library.hooks.js +9 -2
- package/dist/widgets/react/pages/WidgetInstanceEditor.d.ts +1 -1
- package/dist/widgets/react/pages/WidgetInstanceEditor.js +7 -8
- package/dist/widgets/rules.d.ts +14 -2
- package/dist/widgets/rules.js +9 -6
- package/package.json +23 -14
- package/src/themes/default/admin.css +51 -0
- package/src/themes/default/chat.css +21 -0
- package/src/themes/default/manifest.json +29 -0
- package/dist/media/styles.d.ts +0 -4
- package/dist/media/styles.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e27fa91: Removed the `./server` export subpath. It held only the Composio integration, which moves back
|
|
8
|
+
out — now `@jini-ai/integrations/composio`, a subpath of `@jini-ai/integrations`
|
|
9
|
+
(`packages/integrations/`; see that package's own changeset for the full history). `jini.entries`
|
|
10
|
+
drops its `"./server": "node"` entry, and the `@jini-ai/protocol` devDependency (which existed only
|
|
11
|
+
for composio's type-only `JsonValue` references) is removed. No other consumer imported
|
|
12
|
+
`@jini-ai/admin/server` — nothing else needs an import-path change.
|
|
13
|
+
- cc33cf1: Folded `@jini-ai/composio` into `@jini-ai/admin` as a `./server` subpath, and retired the package.
|
|
14
|
+
|
|
15
|
+
Composio's only consumer is the admin surface, so a standalone package was a boundary that bought
|
|
16
|
+
nothing — the same reasoning that retired `@jini-ai/ui-core` into `@jini-ai/ui/core`. All 11 source
|
|
17
|
+
files and 8 test files move as-is; no production logic changed.
|
|
18
|
+
|
|
19
|
+
It lands under `./server`, not `./core`, because it imports `node:fs`, `node:crypto`, and
|
|
20
|
+
`node:path`. `/core` is the framework-free, DOM-free, universal layer, and that property is what
|
|
21
|
+
makes it installable without React — it is not a general-purpose home for anything non-React.
|
|
22
|
+
`jini.entries` now reads `{".": universal, "./core": universal, "./browser": browser,
|
|
23
|
+
"./server": node}`. `@jini-ai/protocol` becomes a **devDependency**: all seven references are
|
|
24
|
+
`import type { JsonValue }`, so admin's consumers pay nothing at runtime for this.
|
|
25
|
+
|
|
26
|
+
**Composio's tests had never been type-checked.** Its `tsconfig.json` set `include: ["src"]` while
|
|
27
|
+
its tests lived in a top-level `tests/` directory, so `tsc` never looked at them, and `vitest` does
|
|
28
|
+
not type-check. Twenty errors under `exactOptionalPropertyTypes: true` — a setting predating the
|
|
29
|
+
package — had been latent since its creation. Admin co-locates tests under `src/**/__tests__/`,
|
|
30
|
+
which `include: ["src"]` does cover, so the move surfaced them; it did not cause them. Nineteen
|
|
31
|
+
were mechanical fixture shapes. The twentieth was not: a `credentialStore.set` fixture in
|
|
32
|
+
`service.contract.test.ts` was missing `accountLabel` **on purpose**, simulating a persisted record
|
|
33
|
+
without one so the test could assert the resulting rejection path. "Fixing" it to satisfy the
|
|
34
|
+
compiler made the request match instead of mismatch and broke the test at runtime; it is now
|
|
35
|
+
preserved verbatim behind a documented assertion. No production type was widened to satisfy a
|
|
36
|
+
fixture. `src/server/**` keeps composio's own 100% coverage threshold via a scoped glob rather than
|
|
37
|
+
promoting it package-wide onto `/core` and `/browser`, which never carried it.
|
|
38
|
+
|
|
39
|
+
**`@jini-ai/composio` is retired, not versioned forward.** It was published (0.1.0, 0.2.0, 0.2.1 —
|
|
40
|
+
a longer public life than ui-core's single version), but has zero consumers: no source file,
|
|
41
|
+
package.json, or lockfile in either this repo or Tovu has ever referenced it, verified with a `-S`
|
|
42
|
+
pickaxe over full git history rather than current state alone. Deleting the source does **not**
|
|
43
|
+
unpublish anything — 0.2.1 stays installable and no existing build breaks — so a deprecation-stub
|
|
44
|
+
package was judged not worth keeping alive for a name nothing needs. Whoever runs the next release
|
|
45
|
+
should follow up against the registry with:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
npm deprecate @jini-ai/composio "Merged into @jini-ai/admin; import from '@jini-ai/admin/server' instead."
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
One caution for anyone re-examining that consumer question later: composio's CHANGELOG for 0.2.1
|
|
52
|
+
says "Verified against a real external consumer (Tovu...)", which reads like direct evidence of a
|
|
53
|
+
dependency. It is not. That exact sentence appears verbatim in 23 other packages' CHANGELOGs — it
|
|
54
|
+
is boilerplate from a repo-wide `main`/`types` resolution fix that was batch-applied, not a
|
|
55
|
+
composio-specific claim.
|
|
56
|
+
|
|
57
|
+
Composio's `source-map.md` is carried forward to `src/server/source-map.md`. Its package-level
|
|
58
|
+
`UNLOCKED.md` is not: it tracked admission evidence for the locked/incubating gate removed
|
|
59
|
+
2026-07-28, and its one substantive point is covered in more detail by `source-map.md`. The root
|
|
60
|
+
`UNLOCKED.md`'s composio entry is deliberately left untouched as historical record, per that file's
|
|
61
|
+
own header.
|
|
62
|
+
|
|
63
|
+
- 82ed2d3: Media, SEO and widgets require FetchQueryProvider. Successful media refreshes clear errors; editor reads request fresh versions. Remove redundant settings-loader/provider reads and limit media invalidation to the library. Retain the exported headless entry-picker controller as deprecated pending owner-approved removal.
|
|
64
|
+
- ea8d883: Restore host media-prefix invalidation while isolating provider queries, and validate single-owner SEO form reads.
|
|
65
|
+
- ea8d883: Skip cross-origin image HEAD probes, translate social image warnings through the SEO translator, and use the existing warning notice class.
|
|
66
|
+
- 1c5d476: Hydrate reopened SEO forms from the fresh settings read through the shared query loader so cached default values cannot hide newer settings.
|
|
67
|
+
|
|
68
|
+
## 0.4.13 — 2026-10-22
|
|
69
|
+
|
|
70
|
+
- 2026-10-22: Consent banner editor sections and preview, redirect list fixes, media lightbox via @jini-ai/ui, media provider tab fixes, coverage.
|
|
71
|
+
|
|
3
72
|
## 0.4.12 — 2026-10-16
|
|
4
73
|
|
|
5
74
|
- 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).
|
|
@@ -58,7 +127,7 @@
|
|
|
58
127
|
|
|
59
128
|
## Unreleased
|
|
60
129
|
|
|
61
|
-
- Add portable Forms admin screens, HTTP/memory adapters and core-port conformance under ./forms
|
|
130
|
+
- Add portable Forms admin screens, HTTP/memory adapters and core-port conformance under ./forms\*.
|
|
62
131
|
- Add composable comments admin queue/settings module with HTTP and memory adapters and API conformance.
|
|
63
132
|
- Add portable redirects admin module, React list, HTTP/memory adapters and API conformance.
|
|
64
133
|
- Add portable SEO admin settings, sitemap viewer, entry overrides and analysis with HTTP/memory adapters and API conformance.
|
|
@@ -74,7 +143,6 @@
|
|
|
74
143
|
|
|
75
144
|
- Adopt shared kernel contracts and preserve the module rationale in neutral documentation.
|
|
76
145
|
|
|
77
|
-
|
|
78
146
|
- Complete object-argument calls in Sidebar, RowMenu and ConfirmDialog, preserving
|
|
79
147
|
preferences, routing, tone precedence, selection and agent handle behavior.
|
|
80
148
|
- Merge every per-request `HeadersInit` form through `Headers`, with case-insensitive
|
|
@@ -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 (
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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(
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
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
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
}
|
|
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 }
|
|
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 }),
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
|
13
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
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
|