@happyvertical/smrt-agents 0.33.1 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -136,4 +136,5 @@ which work is an intentional intervention.
136
136
  - `@happyvertical/ai` -- AI client (SDK)
137
137
  - `@happyvertical/files` -- Filesystem utilities (SDK)
138
138
  - `@happyvertical/utils` -- Shared utilities (SDK)
139
- - Peer (optional): `@happyvertical/smrt-svelte`, `svelte`
139
+ - `@happyvertical/smrt-ui` -- UI runtime (i18n client, primitives, module registry) for the optional `./svelte` components, including the agent-admin shells (`AgentAdminPanel`, `AgentAdminTabs`, `AgentSettingsShell`) that moved here from smrt-svelte in #1589
140
+ - Peer (optional): `svelte`
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1782238520500,
3
+ "timestamp": 1782241028851,
4
4
  "packageName": "@happyvertical/smrt-agents",
5
- "packageVersion": "0.33.1",
5
+ "packageVersion": "0.34.0",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-agents:Agent": {
8
8
  "name": "agent",
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-06-23T18:15:20.912Z",
3
+ "generatedAt": "2026-06-23T18:57:09.201Z",
4
4
  "packageName": "@happyvertical/smrt-agents",
5
- "packageVersion": "0.33.1",
5
+ "packageVersion": "0.34.0",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "6eb0824a0f55c5aa3e25d5fda322d43b7b33183ce6aa1bd81b0ec0a3baa7275f",
10
- "packageJson": "4192d74e118c82ce66619134362320ee362a7231e7a5aee2d5433064acfaf2b8",
9
+ "manifest": "4b81fa92d8730fb99fb77e360cfb4a03d7d88ed4172a8eeafd88b103338558fa",
10
+ "packageJson": "57047202a7ff5de95df3a60c7e03996b2339a6ffbebed7b02fc836765c5577ad",
11
11
  "agents": "3cdef62db9f57de1f5675726cdc54b3e64e9d39e67ad316904d08212cf046112"
12
12
  },
13
13
  "exports": [
@@ -17,6 +17,7 @@
17
17
  "./playground",
18
18
  "./server",
19
19
  "./svelte",
20
+ "./svelte/admin",
20
21
  "./ui",
21
22
  "./vite"
22
23
  ],
@@ -36,6 +37,9 @@
36
37
  "@happyvertical/sql": "catalog:",
37
38
  "@sentry/node": "catalog:",
38
39
  "@sveltejs/package": "^2.5.7",
40
+ "@sveltejs/vite-plugin-svelte": "^6.2.4",
41
+ "@testing-library/svelte": "^5.3.1",
42
+ "@testing-library/user-event": "^14.6.1",
39
43
  "@types/node": "25.0.9",
40
44
  "fast-glob": "3.3.3",
41
45
  "svelte-check": "^4.3.5",
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Side-effect-free agent-admin shells.
3
+ *
4
+ * Exposes the agent-admin composites (`AgentAdminPanel` / `AgentAdminTabs` /
5
+ * `AgentSettingsShell`) and the registry types they take WITHOUT the schedule
6
+ * component `ModuleUIRegistry` auto-registration that the `./svelte` barrel runs
7
+ * on import. Consumers that only render the admin shells should import from here
8
+ * — it preserves the side-effect-free semantics of the former
9
+ * `@happyvertical/smrt-svelte/admin` subpath (#1589).
10
+ */
11
+ import type { ComponentProps } from 'svelte';
12
+ import AgentAdminPanel from './components/AgentAdminPanel.svelte';
13
+ import AgentAdminTabs from './components/AgentAdminTabs.svelte';
14
+ import AgentSettingsShell from './components/AgentSettingsShell.svelte';
15
+ export { AgentAdminPanel, AgentAdminTabs, AgentSettingsShell };
16
+ export type AgentAdminPanelProps = ComponentProps<typeof AgentAdminPanel>;
17
+ export type AgentAdminTabsProps = ComponentProps<typeof AgentAdminTabs>;
18
+ export type AgentSettingsShellProps = ComponentProps<typeof AgentSettingsShell>;
19
+ export type { AdminPanelBaseProps, AgentUIComponentRegistry, AgentUISlot, AgentUISlots, } from '../ui.js';
20
+ //# sourceMappingURL=admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/svelte/admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,cAAc,MAAM,oCAAoC,CAAC;AAChE,OAAO,kBAAkB,MAAM,wCAAwC,CAAC;AAExE,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;AAE/D,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AACxE,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAKhF,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,WAAW,EACX,YAAY,GACb,MAAM,UAAU,CAAC"}
@@ -0,0 +1,4 @@
1
+ import AgentAdminPanel from './components/AgentAdminPanel.svelte';
2
+ import AgentAdminTabs from './components/AgentAdminTabs.svelte';
3
+ import AgentSettingsShell from './components/AgentSettingsShell.svelte';
4
+ export { AgentAdminPanel, AgentAdminTabs, AgentSettingsShell };
@@ -0,0 +1,111 @@
1
+ <script lang="ts">
2
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import type {
4
+ AdminPanelBaseProps,
5
+ AgentUIComponentRegistry,
6
+ AgentUISlot,
7
+ } from '../../ui.js';
8
+ import { M } from '../i18n.js';
9
+
10
+ const { t } = useI18n();
11
+
12
+ export interface Props {
13
+ /** The registry to look up components from */
14
+ registry: AgentUIComponentRegistry;
15
+ /** The agent class name (e.g., 'Praeco') */
16
+ agentClass: string;
17
+ /** The slot ID to render */
18
+ slotId: string;
19
+ /** The slot definition */
20
+ slot: AgentUISlot;
21
+ /** The current configuration for this slot */
22
+ config: unknown;
23
+ /** Callback when config is saved */
24
+ onSave?: (config: unknown) => Promise<void>;
25
+ /** Whether the panel is read-only */
26
+ readonly?: boolean;
27
+ /** File-based config defaults */
28
+ fileConfig?: unknown;
29
+ /** Database-persisted config overrides */
30
+ dbConfig?: unknown;
31
+ }
32
+
33
+ const {
34
+ registry,
35
+ agentClass,
36
+ slotId,
37
+ slot,
38
+ config,
39
+ onSave,
40
+ readonly = false,
41
+ fileConfig,
42
+ dbConfig,
43
+ }: Props = $props();
44
+
45
+ const Component = $derived(registry.get(agentClass, slotId));
46
+
47
+ async function handleSave(newConfig: unknown) {
48
+ await onSave?.(newConfig);
49
+ }
50
+ </script>
51
+
52
+ {#if Component}
53
+ <Component
54
+ {config}
55
+ onSave={handleSave}
56
+ {readonly}
57
+ {fileConfig}
58
+ {dbConfig}
59
+ />
60
+ {:else}
61
+ <div class="no-panel">
62
+ <div class="no-panel-icon">⚙️</div>
63
+ <p class="no-panel-message">
64
+ {t(M['ui.agent_admin_panel.no_panel_message'])} <code>{agentClass}.{slotId}</code>
65
+ </p>
66
+ <p class="no-panel-hint">
67
+ {t(M['ui.agent_admin_panel.no_panel_hint'])}
68
+ </p>
69
+ </div>
70
+ {/if}
71
+
72
+ <style>
73
+ .no-panel {
74
+ display: flex;
75
+ flex-direction: column;
76
+ align-items: center;
77
+ justify-content: center;
78
+ padding: 3rem 2rem;
79
+ text-align: center;
80
+ background: var(--smrt-color-surface-container-low, #f8fafc);
81
+ border: 1px dashed var(--smrt-color-outline-variant, #e2e8f0);
82
+ border-radius: var(--smrt-radius-medium, 8px);
83
+ min-height: 200px;
84
+ }
85
+
86
+ .no-panel-icon {
87
+ font-size: var(--smrt-typography-display-medium-size, 2.5rem);
88
+ margin-bottom: 1rem;
89
+ opacity: 0.5;
90
+ }
91
+
92
+ .no-panel-message {
93
+ margin: 0 0 0.5rem 0;
94
+ font-size: var(--smrt-typography-body-large-size, 0.9375rem);
95
+ color: var(--smrt-color-on-surface-variant, #64748b);
96
+ }
97
+
98
+ .no-panel-message code {
99
+ background: var(--smrt-color-surface-container-high, #e2e8f0);
100
+ padding: var(--smrt-spacing-1, 0.125rem) var(--smrt-spacing-2, 0.375rem);
101
+ border-radius: var(--smrt-radius-small, 4px);
102
+ font-family: var(--smrt-font-family-mono, 'SF Mono', Monaco, Consolas, monospace);
103
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
104
+ }
105
+
106
+ .no-panel-hint {
107
+ margin: 0;
108
+ font-size: var(--smrt-typography-body-medium-size, 0.8125rem);
109
+ color: var(--smrt-color-on-surface-variant, #94a3b8);
110
+ }
111
+ </style>
@@ -0,0 +1,25 @@
1
+ import type { AgentUIComponentRegistry, AgentUISlot } from '../../ui.js';
2
+ export interface Props {
3
+ /** The registry to look up components from */
4
+ registry: AgentUIComponentRegistry;
5
+ /** The agent class name (e.g., 'Praeco') */
6
+ agentClass: string;
7
+ /** The slot ID to render */
8
+ slotId: string;
9
+ /** The slot definition */
10
+ slot: AgentUISlot;
11
+ /** The current configuration for this slot */
12
+ config: unknown;
13
+ /** Callback when config is saved */
14
+ onSave?: (config: unknown) => Promise<void>;
15
+ /** Whether the panel is read-only */
16
+ readonly?: boolean;
17
+ /** File-based config defaults */
18
+ fileConfig?: unknown;
19
+ /** Database-persisted config overrides */
20
+ dbConfig?: unknown;
21
+ }
22
+ declare const AgentAdminPanel: import("svelte").Component<Props, {}, "">;
23
+ type AgentAdminPanel = ReturnType<typeof AgentAdminPanel>;
24
+ export default AgentAdminPanel;
25
+ //# sourceMappingURL=AgentAdminPanel.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentAdminPanel.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/AgentAdminPanel.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,wBAAwB,EACxB,WAAW,EACZ,MAAM,aAAa,CAAC;AAIrB,MAAM,WAAW,KAAK;IACpB,8CAA8C;IAC9C,QAAQ,EAAE,wBAAwB,CAAC;IACnC,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,8CAA8C;IAC9C,MAAM,EAAE,OAAO,CAAC;IAChB,oCAAoC;IACpC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA8CD,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -0,0 +1,277 @@
1
+ <script lang="ts">
2
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import type { AgentUIComponentRegistry, AgentUISlots } from '../../ui.js';
4
+ import { M } from '../i18n.js';
5
+ import AgentAdminPanel from './AgentAdminPanel.svelte';
6
+
7
+ const { t } = useI18n();
8
+
9
+ export interface Props {
10
+ /** The registry to look up components from */
11
+ registry: AgentUIComponentRegistry;
12
+ /** The agent class name (e.g., 'Praeco') */
13
+ agentClass: string;
14
+ /** UI slots declared by the agent */
15
+ slots: AgentUISlots;
16
+ /** Config data for each slot (keyed by slotId) */
17
+ configs: Record<string, unknown>;
18
+ /** Callback when a slot config is saved */
19
+ onSave?: (slotId: string, config: unknown) => Promise<void>;
20
+ /** Whether all panels are read-only */
21
+ readonly?: boolean;
22
+ /** File-based configs for each slot */
23
+ fileConfigs?: Record<string, unknown>;
24
+ /** Database configs for each slot */
25
+ dbConfigs?: Record<string, unknown>;
26
+ }
27
+
28
+ const {
29
+ registry,
30
+ agentClass,
31
+ slots,
32
+ configs,
33
+ onSave,
34
+ readonly = false,
35
+ fileConfigs = {},
36
+ dbConfigs = {},
37
+ }: Props = $props();
38
+
39
+ // Sort slots by order, then by label
40
+ const sortedSlots = $derived(
41
+ Object.entries(slots).sort(([, a], [, b]) => {
42
+ const orderA = a.order ?? 99;
43
+ const orderB = b.order ?? 99;
44
+ if (orderA !== orderB) return orderA - orderB;
45
+ return a.label.localeCompare(b.label);
46
+ }),
47
+ );
48
+
49
+ let activeSlotId = $state<string | null>(null);
50
+ let tablistEl: HTMLElement | null = $state(null);
51
+
52
+ // Initialize active slot to first sorted slot
53
+ $effect(() => {
54
+ if (activeSlotId === null && sortedSlots.length > 0) {
55
+ activeSlotId = sortedSlots[0][0];
56
+ }
57
+ });
58
+
59
+ function handleSlotClick(slotId: string) {
60
+ activeSlotId = slotId;
61
+ }
62
+
63
+ /**
64
+ * Handle keyboard navigation for accessibility
65
+ */
66
+ function handleKeydown(event: KeyboardEvent, currentSlotId: string) {
67
+ const enabledSlots = sortedSlots;
68
+ const currentIndex = enabledSlots.findIndex(([id]) => id === currentSlotId);
69
+
70
+ if (currentIndex === -1) return;
71
+
72
+ let nextIndex: number | null = null;
73
+
74
+ switch (event.key) {
75
+ case 'ArrowRight':
76
+ event.preventDefault();
77
+ nextIndex = (currentIndex + 1) % enabledSlots.length;
78
+ break;
79
+ case 'ArrowLeft':
80
+ event.preventDefault();
81
+ nextIndex =
82
+ (currentIndex - 1 + enabledSlots.length) % enabledSlots.length;
83
+ break;
84
+ case 'Home':
85
+ event.preventDefault();
86
+ nextIndex = 0;
87
+ break;
88
+ case 'End':
89
+ event.preventDefault();
90
+ nextIndex = enabledSlots.length - 1;
91
+ break;
92
+ }
93
+
94
+ if (nextIndex !== null && nextIndex !== currentIndex) {
95
+ const [nextSlotId] = enabledSlots[nextIndex];
96
+ activeSlotId = nextSlotId;
97
+ // Focus the next tab after DOM update
98
+ requestAnimationFrame(() => {
99
+ const tabButton = tablistEl?.querySelector(
100
+ `[data-tab-id="${CSS.escape(nextSlotId)}"]`,
101
+ ) as HTMLElement;
102
+ tabButton?.focus();
103
+ });
104
+ }
105
+ }
106
+
107
+ async function handleSave(config: unknown) {
108
+ if (activeSlotId && onSave) {
109
+ await onSave(activeSlotId, config);
110
+ }
111
+ }
112
+ </script>
113
+
114
+ <div class="agent-admin-tabs">
115
+ <div class="tabs-nav" role="tablist" aria-label={t(M['ui.agent_admin_tabs.tablist_label'])} bind:this={tablistEl}>
116
+ {#each sortedSlots as [slotId, slot]}
117
+ <button
118
+ class="tab-button"
119
+ class:active={activeSlotId === slotId}
120
+ role="tab"
121
+ aria-selected={activeSlotId === slotId}
122
+ aria-controls="panel-{slotId}"
123
+ id="tab-{slotId}"
124
+ data-tab-id={slotId}
125
+ tabindex={activeSlotId === slotId ? 0 : -1}
126
+ onclick={() => handleSlotClick(slotId)}
127
+ onkeydown={(e) => handleKeydown(e, slotId)}
128
+ >
129
+ {#if slot.icon}
130
+ <span class="tab-icon" aria-hidden="true">{slot.icon}</span>
131
+ {/if}
132
+ <span class="tab-label">{slot.label}</span>
133
+ </button>
134
+ {/each}
135
+ </div>
136
+
137
+ <div class="tab-content">
138
+ {#if activeSlotId && slots[activeSlotId]}
139
+ {@const activeSlot = slots[activeSlotId]}
140
+ <div
141
+ id="panel-{activeSlotId}"
142
+ class="tab-panel"
143
+ role="tabpanel"
144
+ aria-labelledby="tab-{activeSlotId}"
145
+ >
146
+ {#if activeSlot.description}
147
+ <p class="slot-description">{activeSlot.description}</p>
148
+ {/if}
149
+
150
+ <AgentAdminPanel
151
+ {registry}
152
+ {agentClass}
153
+ slotId={activeSlotId}
154
+ slot={activeSlot}
155
+ config={configs[activeSlotId] ?? {}}
156
+ onSave={handleSave}
157
+ {readonly}
158
+ fileConfig={fileConfigs[activeSlotId]}
159
+ dbConfig={dbConfigs[activeSlotId]}
160
+ />
161
+ </div>
162
+ {:else}
163
+ <div class="no-slots">
164
+ <p>{t(M['ui.agent_admin_tabs.no_slots'])}</p>
165
+ </div>
166
+ {/if}
167
+ </div>
168
+ </div>
169
+
170
+ <style>
171
+ .agent-admin-tabs {
172
+ display: flex;
173
+ flex-direction: column;
174
+ gap: 1rem;
175
+ }
176
+
177
+ .tabs-nav {
178
+ display: flex;
179
+ gap: 0.25rem;
180
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #e2e8f0);
181
+ padding-bottom: 0;
182
+ }
183
+
184
+ .tab-button {
185
+ display: flex;
186
+ align-items: center;
187
+ gap: 0.5rem;
188
+ padding: 0.75rem 1rem;
189
+ border: none;
190
+ background: transparent;
191
+ color: var(--smrt-color-on-surface-variant, #64748b);
192
+ font-size: var(--smrt-typography-label-large-size, 0.875rem);
193
+ font-weight: var(--smrt-typography-weight-medium, 500);
194
+ cursor: pointer;
195
+ border-bottom: 2px solid transparent;
196
+ margin-bottom: -1px;
197
+ transition:
198
+ color var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease),
199
+ border-color var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
200
+ }
201
+
202
+ .tab-button:hover:not(:disabled) {
203
+ color: var(--smrt-color-on-surface, #334155);
204
+ }
205
+
206
+ .tab-button.active {
207
+ color: var(--smrt-color-primary, #3b82f6);
208
+ border-bottom-color: var(--smrt-color-primary, #3b82f6);
209
+ }
210
+
211
+ .tab-button:disabled {
212
+ opacity: 0.38;
213
+ cursor: not-allowed;
214
+ }
215
+
216
+ .tab-button:focus-visible {
217
+ outline: 2px solid var(--smrt-color-primary, #3b82f6);
218
+ outline-offset: 2px;
219
+ }
220
+
221
+ .tab-icon {
222
+ font-size: var(--smrt-typography-title-medium-size, 1rem);
223
+ opacity: 0.8;
224
+ }
225
+
226
+ .tab-label {
227
+ white-space: nowrap;
228
+ }
229
+
230
+ .tab-content {
231
+ min-height: 300px;
232
+ }
233
+
234
+ .tab-panel {
235
+ animation: fadeIn var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease-out);
236
+ }
237
+
238
+ @keyframes fadeIn {
239
+ from {
240
+ opacity: 0;
241
+ transform: translateY(4px);
242
+ }
243
+ to {
244
+ opacity: 1;
245
+ transform: translateY(0);
246
+ }
247
+ }
248
+
249
+ @media (prefers-reduced-motion: reduce) {
250
+ .tab-panel {
251
+ animation: none;
252
+ }
253
+ }
254
+
255
+ .slot-description {
256
+ margin: 0 0 1rem 0;
257
+ padding: 0.75rem 1rem;
258
+ background: var(--smrt-color-primary-container, #f0f9ff);
259
+ border-left: 3px solid var(--smrt-color-primary, #3b82f6);
260
+ border-radius: 0 var(--smrt-radius-md, 8px) var(--smrt-radius-md, 8px) 0;
261
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
262
+ color: var(--smrt-color-on-primary-container, #1e40af);
263
+ }
264
+
265
+ .no-slots {
266
+ display: flex;
267
+ align-items: center;
268
+ justify-content: center;
269
+ min-height: 200px;
270
+ color: var(--smrt-color-on-surface-variant, #64748b);
271
+ font-size: var(--smrt-typography-body-medium-size, 0.9375rem);
272
+ }
273
+
274
+ .no-slots p {
275
+ margin: 0;
276
+ }
277
+ </style>
@@ -0,0 +1,23 @@
1
+ import type { AgentUIComponentRegistry, AgentUISlots } from '../../ui.js';
2
+ export interface Props {
3
+ /** The registry to look up components from */
4
+ registry: AgentUIComponentRegistry;
5
+ /** The agent class name (e.g., 'Praeco') */
6
+ agentClass: string;
7
+ /** UI slots declared by the agent */
8
+ slots: AgentUISlots;
9
+ /** Config data for each slot (keyed by slotId) */
10
+ configs: Record<string, unknown>;
11
+ /** Callback when a slot config is saved */
12
+ onSave?: (slotId: string, config: unknown) => Promise<void>;
13
+ /** Whether all panels are read-only */
14
+ readonly?: boolean;
15
+ /** File-based configs for each slot */
16
+ fileConfigs?: Record<string, unknown>;
17
+ /** Database configs for each slot */
18
+ dbConfigs?: Record<string, unknown>;
19
+ }
20
+ declare const AgentAdminTabs: import("svelte").Component<Props, {}, "">;
21
+ type AgentAdminTabs = ReturnType<typeof AgentAdminTabs>;
22
+ export default AgentAdminTabs;
23
+ //# sourceMappingURL=AgentAdminTabs.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentAdminTabs.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/AgentAdminTabs.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK1E,MAAM,WAAW,KAAK;IACpB,8CAA8C;IAC9C,QAAQ,EAAE,wBAAwB,CAAC;IACnC,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,KAAK,EAAE,YAAY,CAAC;IACpB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,2CAA2C;IAC3C,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAkID,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,322 @@
1
+ <script lang="ts">
2
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import type { AgentUIComponentRegistry, AgentUISlots } from '../../ui.js';
4
+ import { M } from '../i18n.js';
5
+ import AgentAdminTabs from './AgentAdminTabs.svelte';
6
+
7
+ const { t } = useI18n();
8
+
9
+ /**
10
+ * Serialized agent data passed from server
11
+ * (Agent instances can't be passed directly to client)
12
+ */
13
+ interface AgentData {
14
+ id: string;
15
+ name?: string;
16
+ /** Agent class name (e.g., 'Praeco') - determined from _meta_type or explicit */
17
+ agentClass: string;
18
+ /** UI slots for this agent */
19
+ slots: AgentUISlots;
20
+ }
21
+
22
+ export interface Props {
23
+ /** The registry to look up components from */
24
+ registry: AgentUIComponentRegistry;
25
+ /** List of agents to display (serialized data, not instances) */
26
+ agents: AgentData[];
27
+ /** Config data for each agent and slot: agentId -> slotId -> config */
28
+ configs: Record<string, Record<string, unknown>>;
29
+ /** Callback when a config is saved */
30
+ onSave?: (agentId: string, slotId: string, config: unknown) => Promise<void>;
31
+ /** Whether all panels are read-only */
32
+ readonly?: boolean;
33
+ /** File configs: agentId -> slotId -> config */
34
+ fileConfigs?: Record<string, Record<string, unknown>>;
35
+ /** Database configs: agentId -> slotId -> config */
36
+ dbConfigs?: Record<string, Record<string, unknown>>;
37
+ }
38
+
39
+ const {
40
+ registry,
41
+ agents,
42
+ configs,
43
+ onSave,
44
+ readonly = false,
45
+ fileConfigs = {},
46
+ dbConfigs = {},
47
+ }: Props = $props();
48
+
49
+ // Stable per-instance id base so multiple shells don't collide on tab/panel ids.
50
+ const idBase = $props.id();
51
+
52
+ let activeAgentId = $state<string | null>(null);
53
+ let tablistEl: HTMLElement | null = $state(null);
54
+
55
+ // Initialize to first agent
56
+ $effect(() => {
57
+ if (activeAgentId === null && agents.length > 0) {
58
+ activeAgentId = agents[0].id;
59
+ }
60
+ });
61
+
62
+ const activeAgent = $derived(agents.find((a) => a.id === activeAgentId));
63
+
64
+ function handleAgentClick(agentId: string) {
65
+ activeAgentId = agentId;
66
+ }
67
+
68
+ /**
69
+ * Roving-tabindex keyboard navigation for the agent switcher (C8). The list is
70
+ * a vertical tablist, so Up/Down move between agents (plus Home/End); mirrors
71
+ * the horizontal pattern in AgentAdminTabs.
72
+ */
73
+ function handleAgentKeydown(event: KeyboardEvent, currentAgentId: string) {
74
+ const currentIndex = agents.findIndex((a) => a.id === currentAgentId);
75
+ if (currentIndex === -1) return;
76
+
77
+ let nextIndex: number | null = null;
78
+ switch (event.key) {
79
+ case 'ArrowDown':
80
+ case 'ArrowRight':
81
+ event.preventDefault();
82
+ nextIndex = (currentIndex + 1) % agents.length;
83
+ break;
84
+ case 'ArrowUp':
85
+ case 'ArrowLeft':
86
+ event.preventDefault();
87
+ nextIndex = (currentIndex - 1 + agents.length) % agents.length;
88
+ break;
89
+ case 'Home':
90
+ event.preventDefault();
91
+ nextIndex = 0;
92
+ break;
93
+ case 'End':
94
+ event.preventDefault();
95
+ nextIndex = agents.length - 1;
96
+ break;
97
+ }
98
+
99
+ if (nextIndex !== null && nextIndex !== currentIndex) {
100
+ const nextAgentId = agents[nextIndex].id;
101
+ activeAgentId = nextAgentId;
102
+ // Focus the newly-selected tab after the DOM updates.
103
+ requestAnimationFrame(() => {
104
+ const tabButton = tablistEl?.querySelector(
105
+ `[data-agent-id="${CSS.escape(nextAgentId)}"]`,
106
+ ) as HTMLElement | null;
107
+ tabButton?.focus();
108
+ });
109
+ }
110
+ }
111
+
112
+ async function handleSave(slotId: string, config: unknown) {
113
+ if (activeAgentId && onSave) {
114
+ await onSave(activeAgentId, slotId, config);
115
+ }
116
+ }
117
+ </script>
118
+
119
+ <div class="agent-settings-shell" class:single-agent={agents.length === 1}>
120
+ {#if agents.length > 1}
121
+ <aside class="agents-sidebar">
122
+ <h3 class="sidebar-title" id="{idBase}-agents-title">Agents</h3>
123
+ <div
124
+ class="agents-list"
125
+ role="tablist"
126
+ aria-orientation="vertical"
127
+ aria-labelledby="{idBase}-agents-title"
128
+ bind:this={tablistEl}
129
+ >
130
+ {#each agents as agent}
131
+ <button
132
+ class="agent-button"
133
+ class:active={activeAgentId === agent.id}
134
+ role="tab"
135
+ aria-selected={activeAgentId === agent.id}
136
+ aria-controls="{idBase}-panel"
137
+ id="{idBase}-tab-{agent.id}"
138
+ data-agent-id={agent.id}
139
+ tabindex={activeAgentId === agent.id ? 0 : -1}
140
+ onclick={() => handleAgentClick(agent.id)}
141
+ onkeydown={(e) => handleAgentKeydown(e, agent.id)}
142
+ >
143
+ <span class="agent-class">{agent.agentClass}</span>
144
+ {#if agent.name}
145
+ <span class="agent-name">{agent.name}</span>
146
+ {/if}
147
+ </button>
148
+ {/each}
149
+ </div>
150
+ </aside>
151
+ {/if}
152
+
153
+ <main
154
+ class="agent-content"
155
+ id="{idBase}-panel"
156
+ role={agents.length > 1 ? 'tabpanel' : undefined}
157
+ aria-labelledby={agents.length > 1 && activeAgentId
158
+ ? `${idBase}-tab-${activeAgentId}`
159
+ : undefined}
160
+ >
161
+ {#if activeAgent}
162
+ <header class="agent-header">
163
+ <h2 class="agent-title">{activeAgent.agentClass}</h2>
164
+ {#if activeAgent.name}
165
+ <p class="agent-subtitle">{activeAgent.name}</p>
166
+ {/if}
167
+ </header>
168
+
169
+ <AgentAdminTabs
170
+ {registry}
171
+ agentClass={activeAgent.agentClass}
172
+ slots={activeAgent.slots}
173
+ configs={configs[activeAgent.id] ?? {}}
174
+ onSave={handleSave}
175
+ {readonly}
176
+ fileConfigs={fileConfigs[activeAgent.id]}
177
+ dbConfigs={dbConfigs[activeAgent.id]}
178
+ />
179
+ {:else if agents.length === 0}
180
+ <div class="no-agents">
181
+ <div class="no-agents-icon">🤖</div>
182
+ <p class="no-agents-message">{t(M['ui.agent_settings_shell.no_agents_message'])}</p>
183
+ <p class="no-agents-hint">
184
+ {t(M['ui.agent_settings_shell.no_agents_hint'])}
185
+ </p>
186
+ </div>
187
+ {/if}
188
+ </main>
189
+ </div>
190
+
191
+ <style>
192
+ .agent-settings-shell {
193
+ display: grid;
194
+ grid-template-columns: 220px 1fr;
195
+ gap: 1.5rem;
196
+ min-height: 400px;
197
+ }
198
+
199
+ .agent-settings-shell.single-agent {
200
+ grid-template-columns: 1fr;
201
+ }
202
+
203
+ .agents-sidebar {
204
+ background: var(--smrt-color-surface-container-low, #f8fafc);
205
+ border-radius: var(--smrt-radius-medium, 8px);
206
+ padding: var(--smrt-spacing-4, 1rem);
207
+ border: 1px solid var(--smrt-color-outline-variant, #e2e8f0);
208
+ }
209
+
210
+ .sidebar-title {
211
+ margin: 0 0 0.75rem 0;
212
+ padding: 0 0.5rem 0.75rem;
213
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
214
+ font-weight: var(--smrt-typography-weight-semibold, 600);
215
+ text-transform: uppercase;
216
+ letter-spacing: var(--smrt-typography-label-medium-tracking, 0.05em);
217
+ color: var(--smrt-color-on-surface-variant, #64748b);
218
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #e2e8f0);
219
+ }
220
+
221
+ .agents-list {
222
+ display: flex;
223
+ flex-direction: column;
224
+ gap: 0.25rem;
225
+ }
226
+
227
+ .agent-button {
228
+ display: flex;
229
+ flex-direction: column;
230
+ align-items: flex-start;
231
+ gap: 0.125rem;
232
+ padding: 0.625rem 0.75rem;
233
+ border: none;
234
+ background: transparent;
235
+ border-radius: var(--smrt-radius-md, 8px);
236
+ cursor: pointer;
237
+ text-align: left;
238
+ width: 100%;
239
+ transition:
240
+ background-color var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease),
241
+ color var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
242
+ }
243
+
244
+ .agent-button:hover {
245
+ background: var(--smrt-color-surface-container-high, #e2e8f0);
246
+ }
247
+
248
+ .agent-button.active {
249
+ background: var(--smrt-color-primary, #3b82f6);
250
+ }
251
+
252
+ .agent-button.active .agent-class,
253
+ .agent-button.active .agent-name {
254
+ color: var(--smrt-color-on-primary, white);
255
+ }
256
+
257
+ .agent-class {
258
+ font-size: var(--smrt-typography-title-small-size, 0.875rem);
259
+ font-weight: var(--smrt-typography-weight-semibold, 600);
260
+ color: var(--smrt-color-on-surface, #1e293b);
261
+ }
262
+
263
+ .agent-name {
264
+ font-size: var(--smrt-typography-body-small-size, 0.75rem);
265
+ color: var(--smrt-color-on-surface-variant, #64748b);
266
+ }
267
+
268
+ .agent-content {
269
+ min-width: 0;
270
+ }
271
+
272
+ .agent-header {
273
+ margin-bottom: 1.5rem;
274
+ padding-bottom: 1rem;
275
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #e2e8f0);
276
+ }
277
+
278
+ .agent-title {
279
+ margin: 0;
280
+ font-size: var(--smrt-typography-headline-medium-size, 1.5rem);
281
+ font-weight: var(--smrt-typography-weight-semibold, 600);
282
+ color: var(--smrt-color-on-surface);
283
+ }
284
+
285
+ .agent-subtitle {
286
+ margin: 0.25rem 0 0;
287
+ font-size: var(--smrt-typography-body-medium-size, 0.9375rem);
288
+ color: var(--smrt-color-on-surface-variant, #64748b);
289
+ }
290
+
291
+ .no-agents {
292
+ display: flex;
293
+ flex-direction: column;
294
+ align-items: center;
295
+ justify-content: center;
296
+ padding: 4rem 2rem;
297
+ text-align: center;
298
+ background: var(--smrt-color-surface-variant);
299
+ border: 1px dashed var(--smrt-color-outline-variant);
300
+ border-radius: var(--smrt-radius-md, 8px);
301
+ min-height: 300px;
302
+ }
303
+
304
+ .no-agents-icon {
305
+ font-size: var(--smrt-typography-display-medium-size, 3rem);
306
+ margin-bottom: 1rem;
307
+ opacity: 0.5;
308
+ }
309
+
310
+ .no-agents-message {
311
+ margin: 0 0 0.5rem 0;
312
+ font-size: var(--smrt-typography-body-large-size, 1rem);
313
+ color: var(--smrt-color-on-surface-variant, #64748b);
314
+ }
315
+
316
+ .no-agents-hint {
317
+ margin: 0;
318
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
319
+ color: var(--smrt-color-on-surface-variant, #94a3b8);
320
+ max-width: 400px;
321
+ }
322
+ </style>
@@ -0,0 +1,33 @@
1
+ import type { AgentUIComponentRegistry, AgentUISlots } from '../../ui.js';
2
+ /**
3
+ * Serialized agent data passed from server
4
+ * (Agent instances can't be passed directly to client)
5
+ */
6
+ interface AgentData {
7
+ id: string;
8
+ name?: string;
9
+ /** Agent class name (e.g., 'Praeco') - determined from _meta_type or explicit */
10
+ agentClass: string;
11
+ /** UI slots for this agent */
12
+ slots: AgentUISlots;
13
+ }
14
+ export interface Props {
15
+ /** The registry to look up components from */
16
+ registry: AgentUIComponentRegistry;
17
+ /** List of agents to display (serialized data, not instances) */
18
+ agents: AgentData[];
19
+ /** Config data for each agent and slot: agentId -> slotId -> config */
20
+ configs: Record<string, Record<string, unknown>>;
21
+ /** Callback when a config is saved */
22
+ onSave?: (agentId: string, slotId: string, config: unknown) => Promise<void>;
23
+ /** Whether all panels are read-only */
24
+ readonly?: boolean;
25
+ /** File configs: agentId -> slotId -> config */
26
+ fileConfigs?: Record<string, Record<string, unknown>>;
27
+ /** Database configs: agentId -> slotId -> config */
28
+ dbConfigs?: Record<string, Record<string, unknown>>;
29
+ }
30
+ declare const AgentSettingsShell: import("svelte").Component<Props, {}, "">;
31
+ type AgentSettingsShell = ReturnType<typeof AgentSettingsShell>;
32
+ export default AgentSettingsShell;
33
+ //# sourceMappingURL=AgentSettingsShell.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentSettingsShell.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/AgentSettingsShell.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK1E;;;GAGG;AACH,UAAU,SAAS;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,MAAM,WAAW,KAAK;IACpB,8CAA8C;IAC9C,QAAQ,EAAE,wBAAwB,CAAC;IACnC,iEAAiE;IACjE,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACjD,sCAAsC;IACtC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACrD;AAwID,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,94 @@
1
+ // @vitest-environment jsdom
2
+ /**
3
+ * Regression: the agent switcher is a proper ARIA tablist (C8).
4
+ *
5
+ * It was a <nav> of <button>s acting as single-select tabs, with no
6
+ * role/aria-selected/roving-tabindex/arrow-key nav — unlike sibling
7
+ * AgentAdminTabs which does the full tablist. The fix adds role="tablist" +
8
+ * role="tab" + aria-selected + roving tabindex + Up/Down/Home/End navigation,
9
+ * and marks the content panel role="tabpanel".
10
+ */
11
+ import { createUIRegistry } from '@happyvertical/smrt-agents/ui';
12
+ import { render, screen, waitFor, within } from '@testing-library/svelte';
13
+ import userEvent from '@testing-library/user-event';
14
+ import { describe, expect, it } from 'vitest';
15
+ import AgentSettingsShell from '../AgentSettingsShell.svelte';
16
+ /**
17
+ * Roving focus moves via requestAnimationFrame, so wait for the expected tab to
18
+ * actually receive focus before dispatching the next key (otherwise the next
19
+ * keydown fires on the previously-focused tab).
20
+ */
21
+ async function expectFocused(el) {
22
+ await waitFor(() => expect(el).toHaveFocus());
23
+ }
24
+ function agents() {
25
+ return [
26
+ { id: 'a1', agentClass: 'Praeco', name: 'Alpha', slots: {} },
27
+ { id: 'a2', agentClass: 'Caelus', name: 'Beta', slots: {} },
28
+ { id: 'a3', agentClass: 'Nimbus', name: 'Gamma', slots: {} },
29
+ ];
30
+ }
31
+ function renderShell() {
32
+ return render(AgentSettingsShell, {
33
+ props: {
34
+ registry: createUIRegistry(),
35
+ agents: agents(),
36
+ configs: {},
37
+ },
38
+ });
39
+ }
40
+ /**
41
+ * The shell embeds AgentAdminTabs (its own slot tablist), so scope queries to
42
+ * the agent-switcher tablist — identified by its "Agents" accessible name.
43
+ */
44
+ function switcher() {
45
+ return screen.getByRole('tablist', { name: 'Agents' });
46
+ }
47
+ function switcherTabs() {
48
+ return within(switcher()).getAllByRole('tab');
49
+ }
50
+ describe('AgentSettingsShell — agent switcher tablist (C8)', () => {
51
+ it('renders a tablist of tabs with the first selected', () => {
52
+ renderShell();
53
+ expect(switcher()).toBeInTheDocument();
54
+ const tabs = switcherTabs();
55
+ expect(tabs).toHaveLength(3);
56
+ expect(tabs[0]).toHaveAttribute('aria-selected', 'true');
57
+ expect(tabs[1]).toHaveAttribute('aria-selected', 'false');
58
+ });
59
+ it('uses a roving tabindex (only the selected tab is tabbable)', () => {
60
+ renderShell();
61
+ const tabs = switcherTabs();
62
+ expect(tabs[0]).toHaveAttribute('tabindex', '0');
63
+ expect(tabs[1]).toHaveAttribute('tabindex', '-1');
64
+ expect(tabs[2]).toHaveAttribute('tabindex', '-1');
65
+ });
66
+ it('moves selection with ArrowDown / ArrowUp', async () => {
67
+ renderShell();
68
+ switcherTabs()[0].focus();
69
+ await userEvent.keyboard('{ArrowDown}');
70
+ expect(switcherTabs()[1]).toHaveAttribute('aria-selected', 'true');
71
+ await expectFocused(switcherTabs()[1]);
72
+ await userEvent.keyboard('{ArrowUp}');
73
+ expect(switcherTabs()[0]).toHaveAttribute('aria-selected', 'true');
74
+ await expectFocused(switcherTabs()[0]);
75
+ });
76
+ it('jumps to last/first with End / Home', async () => {
77
+ renderShell();
78
+ switcherTabs()[0].focus();
79
+ await userEvent.keyboard('{End}');
80
+ expect(switcherTabs()[2]).toHaveAttribute('aria-selected', 'true');
81
+ await expectFocused(switcherTabs()[2]);
82
+ await userEvent.keyboard('{Home}');
83
+ expect(switcherTabs()[0]).toHaveAttribute('aria-selected', 'true');
84
+ await expectFocused(switcherTabs()[0]);
85
+ });
86
+ it('exposes a tabpanel wired to the selected tab', () => {
87
+ renderShell();
88
+ const panel = screen.getByRole('tabpanel');
89
+ const selectedTab = switcherTabs().find((t) => t.getAttribute('aria-selected') === 'true');
90
+ expect(selectedTab).toBeTruthy();
91
+ expect(panel).toHaveAttribute('aria-labelledby', selectedTab?.id);
92
+ expect(selectedTab).toHaveAttribute('aria-controls', panel.id);
93
+ });
94
+ });
@@ -29,5 +29,11 @@ export declare const M: {
29
29
  readonly 'agents.schedule_list.loading_schedules': "agents.schedule_list.loading_schedules";
30
30
  readonly 'agents.schedule_list.no_schedules_found': "agents.schedule_list.no_schedules_found";
31
31
  readonly 'agents.schedule_list.run_now': "agents.schedule_list.run_now";
32
+ readonly 'ui.agent_admin_panel.no_panel_message': "ui.agent_admin_panel.no_panel_message";
33
+ readonly 'ui.agent_admin_panel.no_panel_hint': "ui.agent_admin_panel.no_panel_hint";
34
+ readonly 'ui.agent_admin_tabs.no_slots': "ui.agent_admin_tabs.no_slots";
35
+ readonly 'ui.agent_admin_tabs.tablist_label': "ui.agent_admin_tabs.tablist_label";
36
+ readonly 'ui.agent_settings_shell.no_agents_message': "ui.agent_settings_shell.no_agents_message";
37
+ readonly 'ui.agent_settings_shell.no_agents_hint': "ui.agent_settings_shell.no_agents_hint";
32
38
  };
33
39
  //# sourceMappingURL=i18n.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CZ,CAAC"}
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2DZ,CAAC"}
@@ -34,4 +34,13 @@ export const M = defineMessages({
34
34
  'agents.schedule_list.loading_schedules': 'Loading schedules...',
35
35
  'agents.schedule_list.no_schedules_found': 'No schedules found',
36
36
  'agents.schedule_list.run_now': 'Run Now',
37
+ // components/AgentAdminPanel.svelte
38
+ 'ui.agent_admin_panel.no_panel_message': 'No admin panel registered for',
39
+ 'ui.agent_admin_panel.no_panel_hint': "Import the agent's admin package to register its panels.",
40
+ // components/AgentAdminTabs.svelte
41
+ 'ui.agent_admin_tabs.no_slots': 'No configuration slots available for this agent.',
42
+ 'ui.agent_admin_tabs.tablist_label': 'Agent configuration tabs',
43
+ // components/AgentSettingsShell.svelte
44
+ 'ui.agent_settings_shell.no_agents_message': 'No agents configured for this site.',
45
+ 'ui.agent_settings_shell.no_agents_hint': 'Agents are discovered by matching their context field to the site domain.',
37
46
  });
@@ -1,23 +1,33 @@
1
1
  /**
2
2
  * Agents Module Svelte Components
3
3
  *
4
- * Optional Svelte UI components for agent schedule management.
5
- * Auto-registers components with ModuleUIRegistry on import.
4
+ * Optional Svelte UI components for agent schedule management and the
5
+ * agent-admin shells (AgentAdminPanel / AgentAdminTabs / AgentSettingsShell)
6
+ * that render UI-slot config panels against the agent UI registry.
7
+ * Schedule components auto-register with ModuleUIRegistry on import; the admin
8
+ * shells are imported directly (not module-registry discoverable).
6
9
  *
7
10
  * @packageDocumentation
8
11
  */
9
12
  import type { ComponentProps } from 'svelte';
13
+ import AgentAdminPanel from './components/AgentAdminPanel.svelte';
14
+ import AgentAdminTabs from './components/AgentAdminTabs.svelte';
10
15
  import AgentDashboard from './components/AgentDashboard.svelte';
11
16
  import AgentRunHistory from './components/AgentRunHistory.svelte';
12
17
  import AgentScheduleForm from './components/AgentScheduleForm.svelte';
13
18
  import AgentScheduleList from './components/AgentScheduleList.svelte';
19
+ import AgentSettingsShell from './components/AgentSettingsShell.svelte';
14
20
  import ScheduleStatusBadge from './components/ScheduleStatusBadge.svelte';
15
- export { AgentDashboard, AgentRunHistory, AgentScheduleForm, AgentScheduleList, ScheduleStatusBadge, };
21
+ export { AgentAdminPanel, AgentAdminTabs, AgentDashboard, AgentRunHistory, AgentScheduleForm, AgentScheduleList, AgentSettingsShell, ScheduleStatusBadge, };
22
+ export type AgentAdminPanelProps = ComponentProps<typeof AgentAdminPanel>;
23
+ export type AgentAdminTabsProps = ComponentProps<typeof AgentAdminTabs>;
16
24
  export type AgentDashboardProps = ComponentProps<typeof AgentDashboard>;
17
25
  export type AgentRunHistoryProps = ComponentProps<typeof AgentRunHistory>;
18
26
  export type AgentScheduleFormProps = ComponentProps<typeof AgentScheduleForm>;
19
27
  export type AgentScheduleListProps = ComponentProps<typeof AgentScheduleList>;
28
+ export type AgentSettingsShellProps = ComponentProps<typeof AgentSettingsShell>;
20
29
  export type ScheduleStatusBadgeProps = ComponentProps<typeof ScheduleStatusBadge>;
30
+ export type { AdminPanelBaseProps, AgentUIComponentRegistry, AgentUISlot, AgentUISlots, } from '../ui.js';
21
31
  export type { AgentDashboardProps as AgentDashboardPropsLegacy, AgentRunHistoryEntry, AgentRunHistoryProps as AgentRunHistoryPropsLegacy, AgentScheduleData, AgentScheduleFormProps as AgentScheduleFormPropsLegacy, AgentScheduleListProps as AgentScheduleListPropsLegacy, RunStatus, ScheduleFormData, ScheduleStatus, } from './types.js';
22
32
  export { calculateSuccessRate, formatCronExpression, formatDuration, formatRelativeTime, getRunStatusVariant, getScheduleStatusVariant, } from './types.js';
23
33
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAI7C,OAAO,cAAc,MAAM,oCAAoC,CAAC;AAChE,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,iBAAiB,MAAM,uCAAuC,CAAC;AACtE,OAAO,iBAAiB,MAAM,uCAAuC,CAAC;AACtE,OAAO,mBAAmB,MAAM,yCAAyC,CAAC;AAG1E,OAAO,EACL,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,GACpB,CAAC;AAGF,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AACxE,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9E,MAAM,MAAM,wBAAwB,GAAG,cAAc,CACnD,OAAO,mBAAmB,CAC3B,CAAC;AAGF,YAAY,EACV,mBAAmB,IAAI,yBAAyB,EAChD,oBAAoB,EACpB,oBAAoB,IAAI,0BAA0B,EAClD,iBAAiB,EACjB,sBAAsB,IAAI,4BAA4B,EACtD,sBAAsB,IAAI,4BAA4B,EACtD,SAAS,EACT,gBAAgB,EAChB,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAI7C,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,cAAc,MAAM,oCAAoC,CAAC;AAChE,OAAO,cAAc,MAAM,oCAAoC,CAAC;AAChE,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,iBAAiB,MAAM,uCAAuC,CAAC;AACtE,OAAO,iBAAiB,MAAM,uCAAuC,CAAC;AACtE,OAAO,kBAAkB,MAAM,wCAAwC,CAAC;AACxE,OAAO,mBAAmB,MAAM,yCAAyC,CAAC;AAG1E,OAAO,EACL,eAAe,EACf,cAAc,EACd,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,GACpB,CAAC;AAGF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AACxE,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AACxE,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9E,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChF,MAAM,MAAM,wBAAwB,GAAG,cAAc,CACnD,OAAO,mBAAmB,CAC3B,CAAC;AAMF,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,WAAW,EACX,YAAY,GACb,MAAM,UAAU,CAAC;AAGlB,YAAY,EACV,mBAAmB,IAAI,yBAAyB,EAChD,oBAAoB,EACpB,oBAAoB,IAAI,0BAA0B,EAClD,iBAAiB,EACjB,sBAAsB,IAAI,4BAA4B,EACtD,sBAAsB,IAAI,4BAA4B,EACtD,SAAS,EACT,gBAAgB,EAChB,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
@@ -1,21 +1,27 @@
1
1
  /**
2
2
  * Agents Module Svelte Components
3
3
  *
4
- * Optional Svelte UI components for agent schedule management.
5
- * Auto-registers components with ModuleUIRegistry on import.
4
+ * Optional Svelte UI components for agent schedule management and the
5
+ * agent-admin shells (AgentAdminPanel / AgentAdminTabs / AgentSettingsShell)
6
+ * that render UI-slot config panels against the agent UI registry.
7
+ * Schedule components auto-register with ModuleUIRegistry on import; the admin
8
+ * shells are imported directly (not module-registry discoverable).
6
9
  *
7
10
  * @packageDocumentation
8
11
  */
9
12
  import { ModuleUIRegistry } from '@happyvertical/smrt-ui/registry';
10
13
  import { AGENTS_MODULE_META } from '../ui.js';
11
14
  // Import components
15
+ import AgentAdminPanel from './components/AgentAdminPanel.svelte';
16
+ import AgentAdminTabs from './components/AgentAdminTabs.svelte';
12
17
  import AgentDashboard from './components/AgentDashboard.svelte';
13
18
  import AgentRunHistory from './components/AgentRunHistory.svelte';
14
19
  import AgentScheduleForm from './components/AgentScheduleForm.svelte';
15
20
  import AgentScheduleList from './components/AgentScheduleList.svelte';
21
+ import AgentSettingsShell from './components/AgentSettingsShell.svelte';
16
22
  import ScheduleStatusBadge from './components/ScheduleStatusBadge.svelte';
17
23
  // Export components
18
- export { AgentDashboard, AgentRunHistory, AgentScheduleForm, AgentScheduleList, ScheduleStatusBadge, };
24
+ export { AgentAdminPanel, AgentAdminTabs, AgentDashboard, AgentRunHistory, AgentScheduleForm, AgentScheduleList, AgentSettingsShell, ScheduleStatusBadge, };
19
25
  export { calculateSuccessRate, formatCronExpression, formatDuration, formatRelativeTime, getRunStatusVariant, getScheduleStatusVariant, } from './types.js';
20
26
  // Auto-register with ModuleUIRegistry
21
27
  ModuleUIRegistry.registerModule(AGENTS_MODULE_META);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-agents",
3
- "version": "0.33.1",
3
+ "version": "0.34.0",
4
4
  "type": "module",
5
5
  "description": "Agent framework for building autonomous actors in the SMRT ecosystem",
6
6
  "author": "HappyVertical",
@@ -34,6 +34,11 @@
34
34
  "types": "./dist/svelte/index.d.ts",
35
35
  "svelte": "./dist/svelte/index.js",
36
36
  "import": "./dist/svelte/index.js"
37
+ },
38
+ "./svelte/admin": {
39
+ "types": "./dist/svelte/admin.d.ts",
40
+ "svelte": "./dist/svelte/admin.js",
41
+ "import": "./dist/svelte/admin.js"
37
42
  }
38
43
  },
39
44
  "files": [
@@ -53,19 +58,22 @@
53
58
  "@happyvertical/ai": "^0.74.7",
54
59
  "@happyvertical/files": "^0.74.7",
55
60
  "@happyvertical/utils": "^0.74.7",
56
- "@happyvertical/smrt-config": "0.33.1",
57
- "@happyvertical/smrt-core": "0.33.1",
58
- "@happyvertical/smrt-secrets": "0.33.1",
59
- "@happyvertical/smrt-tenancy": "0.33.1",
60
- "@happyvertical/smrt-types": "0.33.1",
61
- "@happyvertical/smrt-ui": "0.33.1",
62
- "@happyvertical/smrt-users": "0.33.1"
61
+ "@happyvertical/smrt-config": "0.34.0",
62
+ "@happyvertical/smrt-secrets": "0.34.0",
63
+ "@happyvertical/smrt-core": "0.34.0",
64
+ "@happyvertical/smrt-tenancy": "0.34.0",
65
+ "@happyvertical/smrt-types": "0.34.0",
66
+ "@happyvertical/smrt-ui": "0.34.0",
67
+ "@happyvertical/smrt-users": "0.34.0"
63
68
  },
64
69
  "devDependencies": {
65
70
  "@happyvertical/logger": "^0.74.7",
66
71
  "@happyvertical/sql": "^0.74.7",
67
72
  "@sentry/node": "^10.51.0",
68
73
  "@sveltejs/package": "^2.5.7",
74
+ "@sveltejs/vite-plugin-svelte": "^6.2.4",
75
+ "@testing-library/svelte": "^5.3.1",
76
+ "@testing-library/user-event": "^14.6.1",
69
77
  "@types/node": "25.0.9",
70
78
  "fast-glob": "3.3.3",
71
79
  "svelte-check": "^4.3.5",
@@ -73,7 +81,7 @@
73
81
  "typescript": "^5.9.3",
74
82
  "vite": "^7.3.1",
75
83
  "vitest": "^4.0.17",
76
- "@happyvertical/smrt-vitest": "0.33.1"
84
+ "@happyvertical/smrt-vitest": "0.34.0"
77
85
  },
78
86
  "keywords": [
79
87
  "agent",