@happyvertical/smrt-agents 0.33.1 → 0.34.1
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 +2 -1
- package/dist/chunks/{config-BYbOxt24.js → config-JYiYqNE-.js} +18 -8
- package/dist/chunks/config-JYiYqNE-.js.map +1 -0
- package/dist/config.d.ts +11 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/index.js +19 -9
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/schedule.d.ts +11 -2
- package/dist/schedule.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/smrt-knowledge.json +8 -4
- package/dist/svelte/admin.d.ts +20 -0
- package/dist/svelte/admin.d.ts.map +1 -0
- package/dist/svelte/admin.js +4 -0
- package/dist/svelte/components/AgentAdminPanel.svelte +111 -0
- package/dist/svelte/components/AgentAdminPanel.svelte.d.ts +25 -0
- package/dist/svelte/components/AgentAdminPanel.svelte.d.ts.map +1 -0
- package/dist/svelte/components/AgentAdminTabs.svelte +277 -0
- package/dist/svelte/components/AgentAdminTabs.svelte.d.ts +23 -0
- package/dist/svelte/components/AgentAdminTabs.svelte.d.ts.map +1 -0
- package/dist/svelte/components/AgentSettingsShell.svelte +322 -0
- package/dist/svelte/components/AgentSettingsShell.svelte.d.ts +33 -0
- package/dist/svelte/components/AgentSettingsShell.svelte.d.ts.map +1 -0
- package/dist/svelte/components/__tests__/AgentSettingsShell.tablist.test.js +94 -0
- package/dist/svelte/i18n.d.ts +6 -0
- package/dist/svelte/i18n.d.ts.map +1 -1
- package/dist/svelte/i18n.js +9 -0
- package/dist/svelte/index.d.ts +13 -3
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +9 -3
- package/package.json +17 -9
- package/dist/chunks/config-BYbOxt24.js.map +0 -1
|
@@ -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
|
+
});
|
package/dist/svelte/i18n.d.ts
CHANGED
|
@@ -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
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2DZ,CAAC"}
|
package/dist/svelte/i18n.js
CHANGED
|
@@ -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
|
});
|
package/dist/svelte/index.d.ts
CHANGED
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agents Module Svelte Components
|
|
3
3
|
*
|
|
4
|
-
* Optional Svelte UI components for agent schedule management
|
|
5
|
-
*
|
|
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
|
|
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"}
|
package/dist/svelte/index.js
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agents Module Svelte Components
|
|
3
3
|
*
|
|
4
|
-
* Optional Svelte UI components for agent schedule management
|
|
5
|
-
*
|
|
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.
|
|
3
|
+
"version": "0.34.1",
|
|
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.
|
|
57
|
-
"@happyvertical/smrt-core": "0.
|
|
58
|
-
"@happyvertical/smrt-secrets": "0.
|
|
59
|
-
"@happyvertical/smrt-tenancy": "0.
|
|
60
|
-
"@happyvertical/smrt-types": "0.
|
|
61
|
-
"@happyvertical/smrt-
|
|
62
|
-
"@happyvertical/smrt-
|
|
61
|
+
"@happyvertical/smrt-config": "0.34.1",
|
|
62
|
+
"@happyvertical/smrt-core": "0.34.1",
|
|
63
|
+
"@happyvertical/smrt-secrets": "0.34.1",
|
|
64
|
+
"@happyvertical/smrt-tenancy": "0.34.1",
|
|
65
|
+
"@happyvertical/smrt-types": "0.34.1",
|
|
66
|
+
"@happyvertical/smrt-users": "0.34.1",
|
|
67
|
+
"@happyvertical/smrt-ui": "0.34.1"
|
|
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.
|
|
84
|
+
"@happyvertical/smrt-vitest": "0.34.1"
|
|
77
85
|
},
|
|
78
86
|
"keywords": [
|
|
79
87
|
"agent",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-BYbOxt24.js","sources":["../../src/identity.ts","../../src/config.ts"],"sourcesContent":["import { getClassName, ObjectRegistry } from '@happyvertical/smrt-core';\n\n/**\n * Return the canonical agent type identifier for storage and dispatch routing.\n *\n * Uses the registry's qualified name when available and falls back to the input\n * name for dynamically defined or unregistered classes.\n */\nexport function getAgentTypeName(name: string): string {\n const registered = ObjectRegistry.getClass(name);\n return registered?.qualifiedName || registered?.name || name;\n}\n\n/**\n * Return the human-readable class name for UI and logs.\n */\nexport function getAgentClassName(name: string): string {\n const registered = ObjectRegistry.getClass(name);\n return registered?.name || getClassName(name);\n}\n\n/**\n * Return all meaningful aliases for an agent type.\n *\n * The qualified name is first so persistence lookups prefer canonical rows,\n * while the simple class name keeps legacy rows discoverable during migration.\n */\nexport function getAgentTypeAliases(name: string): string[] {\n return Array.from(\n new Set([getAgentTypeName(name), getAgentClassName(name)].filter(Boolean)),\n );\n}\n","/**\n * AgentConfig - Persistent configuration storage for agents\n *\n * This module provides database-backed configuration for agents,\n * enabling consuming apps to persist agent settings.\n *\n * @module\n */\n\nimport {\n field,\n type SmrtClassOptions,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { getAgentTypeName } from './identity.js';\n\n/**\n * AgentConfig stores agent configuration in the database\n *\n * Each config record maps to a UI slot for an agent instance:\n * - agentId: The agent instance's ID\n * - agentClass: The canonical agent type (qualified name when available)\n * - slotId: The configuration slot (e.g., 'sources', 'settings')\n * - configData: JSON object containing the configuration\n *\n * @example\n * ```typescript\n * // Save config for an agent slot\n * const config = new AgentConfig({\n * agentId: agent.id,\n * agentClass: 'Praeco',\n * slotId: 'sources',\n * configData: { scrapers: ['civicweb', 'govstack'] },\n * db: options.db\n * });\n * await config.initialize();\n * await config.save();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'agent_configs',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class AgentConfig extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global agent configs\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * ID of the agent instance this config belongs to\n */\n @field({ type: 'text' })\n agentId: string = '';\n\n /**\n * Canonical agent type for this config (qualified name when available)\n */\n @field({ type: 'text' })\n agentClass: string = '';\n\n /**\n * UI slot ID (e.g., 'sources', 'settings', 'reports')\n */\n @field({ type: 'text' })\n slotId: string = '';\n\n /**\n * Configuration data stored as JSON\n *\n * Sensitive (#1540): agent config blobs routinely carry API keys/credentials,\n * so this is excluded from generated API/MCP responses and rejected as a\n * `where` filter key.\n */\n @field({ type: 'json', sensitive: true })\n configData: Record<string, any> = {};\n\n /**\n * Schema version for future migrations\n */\n @field({ type: 'integer' })\n schemaVersion: number = 1;\n\n /**\n * Load all configs for a specific agent\n *\n * @param agentId - Agent instance ID\n * @param options - Database options\n * @returns Map of slotId → configData\n */\n static async forAgent(\n agentId: string,\n options: SmrtClassOptions,\n ): Promise<Map<string, any>> {\n const configsByAgent = await AgentConfig.forAgents([agentId], options);\n return configsByAgent.get(agentId) ?? new Map();\n }\n\n /**\n * Load configs for multiple agents in a single query.\n *\n * @param agentIds - Agent instance IDs\n * @param options - Database options\n * @returns Map of agentId -> (slotId -> configData)\n */\n static async forAgents(\n agentIds: string[],\n options: SmrtClassOptions,\n ): Promise<Map<string, Map<string, any>>> {\n const configsByAgent = new Map<string, Map<string, any>>();\n if (agentIds.length === 0) {\n return configsByAgent;\n }\n\n const collection = await AgentConfigCollection.create(options);\n const configs = await collection.list({\n where: { 'agentId in': agentIds },\n });\n\n for (const config of configs) {\n if (!configsByAgent.has(config.agentId)) {\n configsByAgent.set(config.agentId, new Map());\n }\n configsByAgent.get(config.agentId)?.set(config.slotId, config.configData);\n }\n\n return configsByAgent;\n }\n\n /**\n * Load config for a specific agent and slot\n *\n * @param agentId - Agent instance ID\n * @param slotId - UI slot ID\n * @param options - Database options\n * @returns Config data or undefined if not found\n */\n static async forSlot(\n agentId: string,\n slotId: string,\n options: SmrtClassOptions,\n ): Promise<any | undefined> {\n const collection = await AgentConfigCollection.create(options);\n const configs = await collection.list({\n where: { agentId, slotId },\n limit: 1,\n });\n return configs[0]?.configData;\n }\n\n /**\n * Save or update config for an agent slot\n *\n * @param data - Config data including agentId, agentClass, slotId, configData\n * @param options - Database options\n * @returns Saved AgentConfig instance\n */\n static async saveSlot(\n data: {\n agentId: string;\n agentClass: string;\n slotId: string;\n configData: Record<string, any>;\n },\n options: SmrtClassOptions,\n ): Promise<AgentConfig> {\n const normalizedAgentClass = getAgentTypeName(data.agentClass);\n const collection = await AgentConfigCollection.create(options);\n\n // Check for existing config using list with where clause\n const existingConfigs = await collection.list({\n where: { agentId: data.agentId, slotId: data.slotId },\n limit: 1,\n });\n\n if (existingConfigs.length > 0) {\n // Update existing\n const existing = existingConfigs[0];\n existing.configData = data.configData;\n existing.agentClass = normalizedAgentClass;\n await existing.save();\n return existing;\n }\n\n // Create new\n const config = await collection.create({\n agentId: data.agentId,\n agentClass: normalizedAgentClass,\n slotId: data.slotId,\n configData: data.configData,\n slug: `${data.agentId}-${data.slotId}`,\n });\n await config.save();\n return config;\n }\n}\n\n/**\n * Collection for AgentConfig objects\n */\nexport class AgentConfigCollection extends SmrtCollection<AgentConfig> {\n static readonly _itemClass = AgentConfig;\n\n /**\n * Find all configs for a specific tenant\n * @param tenantId - Tenant ID to filter by\n * @returns Array of AgentConfig objects for the tenant\n */\n async findByTenant(tenantId: string): Promise<AgentConfig[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global configs (not associated with any tenant)\n * @returns Array of global AgentConfig objects\n */\n async findGlobal(): Promise<AgentConfig[]> {\n return this.list({ where: { tenantId: null } });\n }\n\n /**\n * Find configs for a tenant including global configs\n * @param tenantId - Tenant ID to include\n * @returns Array of AgentConfig objects for the tenant and global configs\n */\n async findWithGlobals(tenantId: string): Promise<AgentConfig[]> {\n return this.query(\n `SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,\n [tenantId],\n );\n }\n}\n"],"names":["tenantId"],"mappings":"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"}
|