@mcoda/agent-setup 0.1.66 → 0.1.71
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 +10 -1
- package/dist/headless/catalog.d.ts +3 -0
- package/dist/headless/catalog.d.ts.map +1 -1
- package/dist/headless/catalog.js +26 -0
- package/dist/headless/normalization.d.ts.map +1 -1
- package/dist/headless/normalization.js +22 -7
- package/dist/react/index.d.ts +4 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +335 -71
- package/dist/server/cliRuntime.d.ts.map +1 -1
- package/dist/server/cliRuntime.js +26 -1
- package/dist/server/inMemoryRuntime.d.ts +1 -0
- package/dist/server/inMemoryRuntime.d.ts.map +1 -1
- package/dist/server/inMemoryRuntime.js +11 -1
- package/dist/server/programmaticRuntime.d.ts +1 -0
- package/dist/server/programmaticRuntime.d.ts.map +1 -1
- package/dist/server/programmaticRuntime.js +27 -1
- package/dist/server/service.d.ts.map +1 -1
- package/dist/server/service.js +43 -19
- package/dist/types.d.ts +6 -3
- package/dist/types.d.ts.map +1 -1
- package/examples/express-server/README.md +4 -0
- package/examples/nextjs-mcoda-agent-setup/README.md +1 -0
- package/examples/vite-react-mcoda-agent-setup/README.md +1 -0
- package/package.json +6 -2
- package/styles/react.css +576 -0
package/dist/react/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const DEFAULT_LABELS = {
|
|
|
7
7
|
saveAssignments: "Save Assignments",
|
|
8
8
|
cloud: "Cloud",
|
|
9
9
|
selfHosted: "Self-hosted",
|
|
10
|
+
workers: "Workers",
|
|
10
11
|
};
|
|
11
12
|
export function McodaAgentSetupPage(props) {
|
|
12
13
|
const labels = { ...DEFAULT_LABELS, ...(props.labels ?? {}) };
|
|
@@ -22,9 +23,11 @@ export function McodaAgentSetupPage(props) {
|
|
|
22
23
|
const [apiKey, setApiKey] = React.useState("");
|
|
23
24
|
const [busy, setBusy] = React.useState(false);
|
|
24
25
|
const [error, setError] = React.useState(null);
|
|
26
|
+
const [statusMessage, setStatusMessage] = React.useState(null);
|
|
25
27
|
const refresh = React.useCallback(async () => {
|
|
26
28
|
setBusy(true);
|
|
27
29
|
setError(null);
|
|
30
|
+
setStatusMessage(null);
|
|
28
31
|
try {
|
|
29
32
|
const next = await props.client.fetchSnapshot();
|
|
30
33
|
setSnapshot(next);
|
|
@@ -45,11 +48,13 @@ export function McodaAgentSetupPage(props) {
|
|
|
45
48
|
return;
|
|
46
49
|
setBusy(true);
|
|
47
50
|
setError(null);
|
|
51
|
+
setStatusMessage(null);
|
|
48
52
|
try {
|
|
49
53
|
const next = await props.client.configureMswarmApiKey({ apiKey });
|
|
50
54
|
setApiKey("");
|
|
51
55
|
setSnapshot(next);
|
|
52
56
|
setAssignments(next.assignments);
|
|
57
|
+
setStatusMessage("mswarm API key saved and agent catalogs synced.");
|
|
53
58
|
}
|
|
54
59
|
catch (caught) {
|
|
55
60
|
setError(caught instanceof Error ? caught.message : String(caught));
|
|
@@ -61,10 +66,12 @@ export function McodaAgentSetupPage(props) {
|
|
|
61
66
|
const syncAgents = async () => {
|
|
62
67
|
setBusy(true);
|
|
63
68
|
setError(null);
|
|
69
|
+
setStatusMessage(null);
|
|
64
70
|
try {
|
|
65
71
|
const next = await props.client.syncAgents();
|
|
66
72
|
setSnapshot(next);
|
|
67
73
|
setAssignments(next.assignments);
|
|
74
|
+
setStatusMessage("Cloud, self-hosted, and Worker agent catalogs synced.");
|
|
68
75
|
}
|
|
69
76
|
catch (caught) {
|
|
70
77
|
setError(caught instanceof Error ? caught.message : String(caught));
|
|
@@ -76,10 +83,12 @@ export function McodaAgentSetupPage(props) {
|
|
|
76
83
|
const saveAssignments = async () => {
|
|
77
84
|
setBusy(true);
|
|
78
85
|
setError(null);
|
|
86
|
+
setStatusMessage(null);
|
|
79
87
|
try {
|
|
80
88
|
const next = await props.client.updateAssignments({ assignments });
|
|
81
89
|
setSnapshot(next);
|
|
82
90
|
setAssignments(next.assignments);
|
|
91
|
+
setStatusMessage("Stage assignments saved.");
|
|
83
92
|
}
|
|
84
93
|
catch (caught) {
|
|
85
94
|
setError(caught instanceof Error ? caught.message : String(caught));
|
|
@@ -88,11 +97,18 @@ export function McodaAgentSetupPage(props) {
|
|
|
88
97
|
setBusy(false);
|
|
89
98
|
}
|
|
90
99
|
};
|
|
91
|
-
return React.createElement("section", { className: joinClasses("mcoda-agent-setup", props.className) }, React.createElement("header", { className: "mcoda-agent-setup__header" }, React.createElement("h1", null, props.title ?? "mcoda Agent Setup"), React.createElement("
|
|
92
|
-
|
|
93
|
-
:
|
|
100
|
+
return React.createElement("section", { className: joinClasses("mcoda-agent-setup", props.className) }, React.createElement("header", { className: "mcoda-agent-setup__header" }, React.createElement("div", { className: "mcoda-agent-setup__title-block" }, React.createElement("p", { className: "mcoda-agent-setup__eyebrow" }, "Runtime routing"), React.createElement("h1", null, props.title ?? "mcoda Agent Setup"), React.createElement("p", null, "Configure mswarm access and assign cloud, self-hosted, or Worker mcoda targets to each runtime stage.")), React.createElement("button", {
|
|
101
|
+
type: "button",
|
|
102
|
+
className: "mcoda-agent-setup__button mcoda-agent-setup__button--secondary",
|
|
103
|
+
disabled: busy,
|
|
104
|
+
onClick: refresh,
|
|
105
|
+
}, busy ? "Refreshing" : "Refresh")), error
|
|
106
|
+
? React.createElement("p", { className: "mcoda-agent-setup__alert mcoda-agent-setup__alert--error" }, error)
|
|
107
|
+
: null, statusMessage
|
|
108
|
+
? React.createElement("p", { className: "mcoda-agent-setup__alert mcoda-agent-setup__alert--success" }, statusMessage)
|
|
109
|
+
: null, React.createElement("div", { className: "mcoda-agent-setup__overview" }, snapshot
|
|
94
110
|
? React.createElement(components.AgentCatalogSummary, { snapshot })
|
|
95
|
-
: React.createElement("
|
|
111
|
+
: React.createElement("div", { className: "mcoda-agent-setup__empty-state" }, busy ? "Loading settings." : "No settings loaded."), React.createElement(components.MswarmAccessCard, {
|
|
96
112
|
apiKey,
|
|
97
113
|
busy,
|
|
98
114
|
labels,
|
|
@@ -100,7 +116,7 @@ export function McodaAgentSetupPage(props) {
|
|
|
100
116
|
onApiKeyChange: setApiKey,
|
|
101
117
|
onSaveKey: saveKey,
|
|
102
118
|
onSyncAgents: syncAgents,
|
|
103
|
-
}), snapshot
|
|
119
|
+
})), snapshot
|
|
104
120
|
? React.createElement(components.StageAgentAssignments, {
|
|
105
121
|
stages,
|
|
106
122
|
snapshot,
|
|
@@ -115,27 +131,65 @@ export function McodaAgentSetupPage(props) {
|
|
|
115
131
|
export function AgentCatalogSummary(props) {
|
|
116
132
|
const { snapshot } = props;
|
|
117
133
|
const warningEntries = Object.entries(snapshot.catalog.errors);
|
|
118
|
-
return React.createElement("
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
134
|
+
return React.createElement("section", { className: "mcoda-agent-setup__summary" }, React.createElement("div", { className: "mcoda-agent-setup__card-heading" }, React.createElement("div", null, React.createElement("h2", null, "Catalog Snapshot"), React.createElement("p", null, `Fetched ${formatTimestamp(snapshot.fetchedAt)}`)), React.createElement("span", {
|
|
135
|
+
className: snapshot.mswarmApiKeyConfigured
|
|
136
|
+
? "mcoda-agent-setup__badge mcoda-agent-setup__badge--success"
|
|
137
|
+
: "mcoda-agent-setup__badge mcoda-agent-setup__badge--warning",
|
|
138
|
+
}, snapshot.mswarmApiKeyConfigured ? "Configured" : "Missing key")), React.createElement("div", { className: "mcoda-agent-setup__metric-grid" }, React.createElement(MetricCard, {
|
|
139
|
+
label: "Provider",
|
|
140
|
+
value: snapshot.provider,
|
|
141
|
+
detail: "active runtime",
|
|
142
|
+
}), React.createElement(MetricCard, {
|
|
143
|
+
label: "mswarm key",
|
|
144
|
+
value: snapshot.mswarmApiKeyLast4
|
|
145
|
+
? `****${snapshot.mswarmApiKeyLast4}`
|
|
146
|
+
: snapshot.mswarmApiKeyConfigured
|
|
147
|
+
? "Configured"
|
|
148
|
+
: "Missing",
|
|
149
|
+
detail: `set ${formatTimestamp(snapshot.mswarmConfiguredAt)}`,
|
|
150
|
+
}), React.createElement(MetricCard, {
|
|
151
|
+
label: "Synced agents",
|
|
152
|
+
value: String(snapshot.catalog.localAgents.length),
|
|
153
|
+
detail: "mcoda registry entries",
|
|
154
|
+
}), React.createElement(MetricCard, {
|
|
155
|
+
label: "Cloud agents",
|
|
156
|
+
value: String(snapshot.catalog.cloudAgents.length),
|
|
157
|
+
detail: "mswarm cloud catalog",
|
|
158
|
+
}), React.createElement(MetricCard, {
|
|
159
|
+
label: "Self-hosted",
|
|
160
|
+
value: String(snapshot.catalog.selfHostedAgents.length),
|
|
161
|
+
detail: `${snapshot.catalog.selfHostedServers.length} servers`,
|
|
162
|
+
}), React.createElement(MetricCard, {
|
|
163
|
+
label: "Workers",
|
|
164
|
+
value: String(snapshot.catalog.workerAgents.length),
|
|
165
|
+
detail: "mswarm Worker catalog",
|
|
166
|
+
})), warningEntries.length
|
|
167
|
+
? React.createElement("ul", { className: "mcoda-agent-setup__warnings", "aria-label": "Catalog warnings" }, warningEntries.map(([key, value]) => React.createElement("li", { key }, `${key}: ${value}`)))
|
|
122
168
|
: null);
|
|
123
169
|
}
|
|
124
170
|
export function MswarmAccessCard(props) {
|
|
125
171
|
const labels = { ...DEFAULT_LABELS, ...(props.labels ?? {}) };
|
|
126
|
-
return React.createElement("section", { className: "mcoda-agent-setup__access" }, React.createElement("h2", null, "mswarm Access"), React.createElement("
|
|
172
|
+
return React.createElement("section", { className: "mcoda-agent-setup__access" }, React.createElement("div", { className: "mcoda-agent-setup__card-heading" }, React.createElement("div", null, React.createElement("h2", null, "mswarm Access"), React.createElement("p", null, props.snapshot?.mswarmApiKeyConfigured
|
|
173
|
+
? "Replace the stored key or resync the catalog."
|
|
174
|
+
: "Add a key to load real cloud, self-hosted, and Worker agents."))), React.createElement("label", { className: "mcoda-agent-setup__field" }, React.createElement("span", null, "API key"), React.createElement("input", {
|
|
127
175
|
type: "password",
|
|
128
176
|
value: props.apiKey,
|
|
129
177
|
autoComplete: "off",
|
|
130
178
|
onChange: (event) => props.onApiKeyChange(event.target.value),
|
|
131
179
|
placeholder: props.snapshot?.mswarmApiKeyConfigured
|
|
132
180
|
? "Replace mswarm API key"
|
|
133
|
-
: "
|
|
134
|
-
}), React.createElement("button", {
|
|
181
|
+
: "sk_prod_mswarm_...",
|
|
182
|
+
})), React.createElement("div", { className: "mcoda-agent-setup__action-row" }, React.createElement("button", {
|
|
135
183
|
type: "button",
|
|
184
|
+
className: "mcoda-agent-setup__button mcoda-agent-setup__button--primary",
|
|
136
185
|
disabled: props.busy || !props.apiKey.trim(),
|
|
137
186
|
onClick: props.onSaveKey,
|
|
138
|
-
}, labels.saveKey), React.createElement("button", {
|
|
187
|
+
}, labels.saveKey), React.createElement("button", {
|
|
188
|
+
type: "button",
|
|
189
|
+
className: "mcoda-agent-setup__button mcoda-agent-setup__button--secondary",
|
|
190
|
+
disabled: props.busy,
|
|
191
|
+
onClick: props.onSyncAgents,
|
|
192
|
+
}, labels.syncAgents)));
|
|
139
193
|
}
|
|
140
194
|
export function StageAgentAssignments(props) {
|
|
141
195
|
const labels = { ...DEFAULT_LABELS, ...(props.labels ?? {}) };
|
|
@@ -151,6 +205,10 @@ export function StageAgentAssignments(props) {
|
|
|
151
205
|
const assignment = props.assignments[stage.stageKey];
|
|
152
206
|
if (!assignment)
|
|
153
207
|
continue;
|
|
208
|
+
if (props.snapshot.catalog.workerAgents.some((agent) => agent.slug === assignment)) {
|
|
209
|
+
nextSources[stage.stageKey] = "worker";
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
154
212
|
const selfHostedServer = props.snapshot.catalog.selfHostedServers.find((server) => server.agents.some((agent) => agent.slug === assignment));
|
|
155
213
|
if (selfHostedServer) {
|
|
156
214
|
nextSources[stage.stageKey] = "self_hosted";
|
|
@@ -161,46 +219,91 @@ export function StageAgentAssignments(props) {
|
|
|
161
219
|
nextSources[stage.stageKey] = "cloud";
|
|
162
220
|
}
|
|
163
221
|
}
|
|
164
|
-
setSources((current) => ({ ...
|
|
165
|
-
setServers((current) => ({ ...
|
|
166
|
-
}, [
|
|
167
|
-
|
|
222
|
+
setSources((current) => ({ ...current, ...nextSources }));
|
|
223
|
+
setServers((current) => ({ ...current, ...nextServers }));
|
|
224
|
+
}, [
|
|
225
|
+
props.assignments,
|
|
226
|
+
props.snapshot.catalog.cloudAgents,
|
|
227
|
+
props.snapshot.catalog.selfHostedServers,
|
|
228
|
+
props.snapshot.catalog.workerAgents,
|
|
229
|
+
props.stages,
|
|
230
|
+
]);
|
|
231
|
+
return React.createElement("section", { className: "mcoda-agent-setup__assignments" }, React.createElement("div", { className: "mcoda-agent-setup__card-heading" }, React.createElement("div", null, React.createElement("h2", null, "Stage Assignments"), React.createElement("p", null, "Select the agent that should run each app workflow stage.")), React.createElement("button", {
|
|
232
|
+
type: "button",
|
|
233
|
+
className: "mcoda-agent-setup__button mcoda-agent-setup__button--success",
|
|
234
|
+
disabled: props.busy,
|
|
235
|
+
onClick: props.onSaveAssignments,
|
|
236
|
+
}, labels.saveAssignments)), React.createElement("div", { className: "mcoda-agent-setup__stage-table-wrap" }, React.createElement("table", { className: "mcoda-agent-setup__stage-table" }, React.createElement("thead", null, React.createElement("tr", null, React.createElement("th", null, "Stage"), React.createElement("th", null, "Source"), React.createElement("th", null, "Self-hosted server"), React.createElement("th", null, "Agent"), React.createElement("th", null, "Status"))), React.createElement("tbody", null, props.stages.map((stage) => {
|
|
237
|
+
const selectedSlug = props.assignments[stage.stageKey] ?? "";
|
|
168
238
|
const source = sources[stage.stageKey] ??
|
|
169
|
-
(stage.preferredSource
|
|
239
|
+
defaultAssignmentSource(stage.preferredSource);
|
|
170
240
|
const serverId = servers[stage.stageKey];
|
|
171
|
-
const server = props.snapshot.catalog.selfHostedServers.find((candidate) => candidate.id === serverId);
|
|
241
|
+
const server = props.snapshot.catalog.selfHostedServers.find((candidate) => candidate.id === serverId) ?? props.snapshot.catalog.selfHostedServers[0];
|
|
172
242
|
const agents = source === "cloud"
|
|
173
243
|
? props.snapshot.catalog.cloudAgents
|
|
174
|
-
:
|
|
175
|
-
|
|
244
|
+
: source === "worker"
|
|
245
|
+
? props.snapshot.catalog.workerAgents
|
|
246
|
+
: server?.agents ?? [];
|
|
247
|
+
const allSelectableAgents = [
|
|
248
|
+
...props.snapshot.catalog.cloudAgents,
|
|
249
|
+
...props.snapshot.catalog.selfHostedServers.flatMap((candidate) => candidate.agents),
|
|
250
|
+
...props.snapshot.catalog.workerAgents,
|
|
251
|
+
];
|
|
252
|
+
const selectedAgent = allSelectableAgents.find((agent) => agent.slug === selectedSlug);
|
|
253
|
+
const agentSelectValue = agents.some((agent) => agent.slug === selectedSlug)
|
|
254
|
+
? selectedSlug
|
|
255
|
+
: "";
|
|
256
|
+
const onSourceChange = (value) => {
|
|
257
|
+
setSources((current) => ({ ...current, [stage.stageKey]: value }));
|
|
258
|
+
const nextSlug = value === "cloud"
|
|
259
|
+
? props.snapshot.catalog.cloudAgents[0]?.slug
|
|
260
|
+
: value === "worker"
|
|
261
|
+
? props.snapshot.catalog.workerAgents[0]?.slug
|
|
262
|
+
: firstSelfHostedAgentSlug(props.snapshot.catalog.selfHostedServers);
|
|
263
|
+
props.onAssignmentChange(stage.stageKey, nextSlug ?? null);
|
|
264
|
+
};
|
|
265
|
+
const onServerChange = (value) => {
|
|
266
|
+
setServers((current) => ({ ...current, [stage.stageKey]: value }));
|
|
267
|
+
const nextServer = props.snapshot.catalog.selfHostedServers.find((candidate) => candidate.id === value);
|
|
268
|
+
props.onAssignmentChange(stage.stageKey, nextServer?.agents[0]?.slug ?? null);
|
|
269
|
+
};
|
|
270
|
+
return React.createElement("tr", { key: stage.stageKey }, React.createElement("td", { className: "mcoda-agent-setup__stage-cell" }, React.createElement("strong", null, stage.displayName), stage.description
|
|
176
271
|
? React.createElement("p", null, stage.description)
|
|
177
|
-
: null), React.createElement(sourceSelect, {
|
|
272
|
+
: null, React.createElement("code", null, stage.stageKey)), React.createElement("td", null, React.createElement(sourceSelect, {
|
|
178
273
|
value: source,
|
|
179
274
|
labels,
|
|
180
|
-
onChange:
|
|
181
|
-
}), source === "self_hosted"
|
|
275
|
+
onChange: onSourceChange,
|
|
276
|
+
})), React.createElement("td", null, source === "self_hosted"
|
|
182
277
|
? React.createElement(serverSelect, {
|
|
183
278
|
servers: props.snapshot.catalog.selfHostedServers,
|
|
184
|
-
value:
|
|
185
|
-
onChange:
|
|
279
|
+
value: server?.id ?? "",
|
|
280
|
+
onChange: onServerChange,
|
|
186
281
|
})
|
|
187
|
-
: null, React.createElement(agentSelect, {
|
|
282
|
+
: React.createElement("span", { className: "mcoda-agent-setup__muted-pill" }, source === "worker" ? "Worker catalog" : "Cloud catalog")), React.createElement("td", null, React.createElement(agentSelect, {
|
|
188
283
|
agents,
|
|
189
|
-
value:
|
|
284
|
+
value: agentSelectValue,
|
|
190
285
|
onChange: (slug) => props.onAssignmentChange(stage.stageKey, slug || null),
|
|
191
|
-
}))
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}, labels.saveAssignments));
|
|
286
|
+
})), React.createElement("td", null, React.createElement(AgentStatusBadge, {
|
|
287
|
+
agent: selectedAgent,
|
|
288
|
+
slug: selectedSlug,
|
|
289
|
+
})));
|
|
290
|
+
})))));
|
|
197
291
|
}
|
|
198
292
|
export function AgentSourceSelect(props) {
|
|
199
293
|
const labels = { ...DEFAULT_LABELS, ...(props.labels ?? {}) };
|
|
200
|
-
return React.createElement("
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
294
|
+
return React.createElement("div", { className: "mcoda-agent-setup__source", role: "group", "aria-label": "Agent source" }, React.createElement("button", {
|
|
295
|
+
type: "button",
|
|
296
|
+
"aria-pressed": props.value === "cloud",
|
|
297
|
+
onClick: () => props.onChange("cloud"),
|
|
298
|
+
}, labels.cloud), React.createElement("button", {
|
|
299
|
+
type: "button",
|
|
300
|
+
"aria-pressed": props.value === "self_hosted",
|
|
301
|
+
onClick: () => props.onChange("self_hosted"),
|
|
302
|
+
}, labels.selfHosted), React.createElement("button", {
|
|
303
|
+
type: "button",
|
|
304
|
+
"aria-pressed": props.value === "worker",
|
|
305
|
+
onClick: () => props.onChange("worker"),
|
|
306
|
+
}, labels.workers));
|
|
204
307
|
}
|
|
205
308
|
export function SelfHostedServerSelect(props) {
|
|
206
309
|
const [query, setQuery] = React.useState("");
|
|
@@ -219,16 +322,27 @@ export function SelfHostedServerSelect(props) {
|
|
|
219
322
|
})
|
|
220
323
|
: null, React.createElement("select", {
|
|
221
324
|
value: props.value,
|
|
325
|
+
disabled: filteredServers.length === 0,
|
|
222
326
|
onChange: (event) => props.onChange(event.target.value),
|
|
223
|
-
}, React.createElement("option", { value: "" }, "Select server"), filteredServers.map((server) => React.createElement("option", { key: server.id, value: server.id }, `${server.label} (${server.agentCount})`))));
|
|
327
|
+
}, React.createElement("option", { value: "" }, filteredServers.length ? "Select server" : "No servers synced"), filteredServers.map((server) => React.createElement("option", { key: server.id, value: server.id }, `${server.label} (${server.agentCount})`))));
|
|
224
328
|
}
|
|
225
329
|
export function AgentSearchSelect(props) {
|
|
226
330
|
const [query, setQuery] = React.useState("");
|
|
227
331
|
const [scrollTop, setScrollTop] = React.useState(0);
|
|
228
332
|
const [activeIndex, setActiveIndex] = React.useState(0);
|
|
333
|
+
const [open, setOpen] = React.useState(false);
|
|
334
|
+
const triggerRef = React.useRef(null);
|
|
335
|
+
const panelRef = React.useRef(null);
|
|
336
|
+
const searchRef = React.useRef(null);
|
|
337
|
+
const [panelFrame, setPanelFrame] = React.useState({
|
|
338
|
+
left: 0,
|
|
339
|
+
maxHeight: 420,
|
|
340
|
+
top: 0,
|
|
341
|
+
width: 360,
|
|
342
|
+
});
|
|
229
343
|
const filtered = React.useMemo(() => filterAgentOptions(props.agents, query), [props.agents, query]);
|
|
230
|
-
const rowHeight = props.rowHeight ??
|
|
231
|
-
const viewportHeight = props.viewportHeight ??
|
|
344
|
+
const rowHeight = props.rowHeight ?? 64;
|
|
345
|
+
const viewportHeight = Math.min(props.viewportHeight ?? 320, panelFrame.maxHeight - 74);
|
|
232
346
|
const windowed = getVirtualAgentWindow(filtered, {
|
|
233
347
|
scrollTop,
|
|
234
348
|
rowHeight,
|
|
@@ -237,10 +351,62 @@ export function AgentSearchSelect(props) {
|
|
|
237
351
|
const selected = props.agents.find((agent) => agent.slug === props.value);
|
|
238
352
|
React.useEffect(() => {
|
|
239
353
|
setActiveIndex(0);
|
|
354
|
+
setScrollTop(0);
|
|
240
355
|
}, [query, props.agents]);
|
|
356
|
+
React.useEffect(() => {
|
|
357
|
+
if (!open)
|
|
358
|
+
return;
|
|
359
|
+
const updatePanelFrame = () => {
|
|
360
|
+
const trigger = triggerRef.current;
|
|
361
|
+
if (!trigger)
|
|
362
|
+
return;
|
|
363
|
+
const rect = trigger.getBoundingClientRect();
|
|
364
|
+
const viewportWidth = window.innerWidth;
|
|
365
|
+
const viewportHeight = window.innerHeight;
|
|
366
|
+
const width = Math.min(Math.max(rect.width, 380), Math.max(280, viewportWidth - 16));
|
|
367
|
+
const left = Math.min(Math.max(8, rect.left), Math.max(8, viewportWidth - width - 8));
|
|
368
|
+
const spaceBelow = viewportHeight - rect.bottom - 12;
|
|
369
|
+
const spaceAbove = rect.top - 12;
|
|
370
|
+
const openBelow = spaceBelow >= 280 || spaceBelow >= spaceAbove;
|
|
371
|
+
const maxHeight = Math.min(420, Math.max(openBelow ? spaceBelow : spaceAbove, 180));
|
|
372
|
+
const top = openBelow
|
|
373
|
+
? Math.min(rect.bottom + 6, viewportHeight - maxHeight - 8)
|
|
374
|
+
: Math.max(8, rect.top - maxHeight - 6);
|
|
375
|
+
setPanelFrame({ left, maxHeight, top, width });
|
|
376
|
+
};
|
|
377
|
+
updatePanelFrame();
|
|
378
|
+
searchRef.current?.focus();
|
|
379
|
+
window.addEventListener("resize", updatePanelFrame);
|
|
380
|
+
window.addEventListener("scroll", updatePanelFrame, true);
|
|
381
|
+
return () => {
|
|
382
|
+
window.removeEventListener("resize", updatePanelFrame);
|
|
383
|
+
window.removeEventListener("scroll", updatePanelFrame, true);
|
|
384
|
+
};
|
|
385
|
+
}, [open]);
|
|
386
|
+
React.useEffect(() => {
|
|
387
|
+
if (!open)
|
|
388
|
+
return;
|
|
389
|
+
const onPointerDown = (event) => {
|
|
390
|
+
const target = event.target;
|
|
391
|
+
if (triggerRef.current?.contains(target) ||
|
|
392
|
+
panelRef.current?.contains(target)) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
setOpen(false);
|
|
396
|
+
};
|
|
397
|
+
document.addEventListener("mousedown", onPointerDown);
|
|
398
|
+
return () => document.removeEventListener("mousedown", onPointerDown);
|
|
399
|
+
}, [open]);
|
|
400
|
+
const selectAgent = (agent) => {
|
|
401
|
+
props.onChange(agent.slug);
|
|
402
|
+
setOpen(false);
|
|
403
|
+
setQuery("");
|
|
404
|
+
};
|
|
241
405
|
const onKeyDown = (event) => {
|
|
242
406
|
if (event.key === "Escape") {
|
|
407
|
+
setOpen(false);
|
|
243
408
|
setQuery("");
|
|
409
|
+
triggerRef.current?.focus();
|
|
244
410
|
return;
|
|
245
411
|
}
|
|
246
412
|
if (event.key === "ArrowDown") {
|
|
@@ -255,38 +421,136 @@ export function AgentSearchSelect(props) {
|
|
|
255
421
|
}
|
|
256
422
|
if (event.key === "Enter" && filtered[activeIndex]) {
|
|
257
423
|
event.preventDefault();
|
|
258
|
-
|
|
424
|
+
selectAgent(filtered[activeIndex]);
|
|
259
425
|
}
|
|
260
426
|
};
|
|
261
|
-
return React.createElement("div", { className: "mcoda-agent-setup__agent-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
:
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
427
|
+
return React.createElement("div", { className: "mcoda-agent-setup__agent-combobox" }, React.createElement("button", {
|
|
428
|
+
ref: triggerRef,
|
|
429
|
+
type: "button",
|
|
430
|
+
className: "mcoda-agent-setup__agent-trigger",
|
|
431
|
+
"aria-haspopup": "listbox",
|
|
432
|
+
"aria-expanded": open,
|
|
433
|
+
disabled: props.agents.length === 0,
|
|
434
|
+
onClick: () => setOpen((current) => !current),
|
|
435
|
+
}, React.createElement("span", { className: "mcoda-agent-setup__agent-trigger-main" }, selected
|
|
436
|
+
? React.createElement(React.Fragment, null, React.createElement("strong", null, agentTitle(selected)), React.createElement("small", null, agentMetadata(selected)))
|
|
437
|
+
: React.createElement("strong", null, props.agents.length ? "Select agent" : "No agents synced")), React.createElement("span", { className: "mcoda-agent-setup__chevron" }, "⌄")), open
|
|
438
|
+
? React.createElement("div", {
|
|
439
|
+
className: "mcoda-agent-setup__agent-panel",
|
|
440
|
+
ref: panelRef,
|
|
441
|
+
style: {
|
|
442
|
+
left: panelFrame.left,
|
|
443
|
+
maxHeight: panelFrame.maxHeight,
|
|
444
|
+
top: panelFrame.top,
|
|
445
|
+
width: panelFrame.width,
|
|
446
|
+
},
|
|
447
|
+
}, React.createElement("div", { className: "mcoda-agent-setup__agent-panel-search" }, React.createElement("input", {
|
|
448
|
+
ref: searchRef,
|
|
449
|
+
value: query,
|
|
450
|
+
onChange: (event) => setQuery(event.target.value),
|
|
451
|
+
onKeyDown,
|
|
452
|
+
"aria-label": "Search agents",
|
|
453
|
+
placeholder: "Search agent, model, provider, or slug",
|
|
454
|
+
})), React.createElement("div", { className: "mcoda-agent-setup__agent-count" }, filtered.length
|
|
455
|
+
? `Showing all ${filtered.length} agents`
|
|
456
|
+
: "No agents match this search"), React.createElement("div", {
|
|
457
|
+
className: "mcoda-agent-setup__agent-list",
|
|
458
|
+
role: "listbox",
|
|
459
|
+
style: { maxHeight: viewportHeight, overflowY: "auto" },
|
|
460
|
+
onKeyDown,
|
|
461
|
+
onScroll: (event) => setScrollTop(event.currentTarget.scrollTop),
|
|
462
|
+
}, React.createElement("div", { style: { height: windowed.beforeHeight } }), windowed.items.map((agent, index) => {
|
|
463
|
+
const absoluteIndex = windowed.startIndex + index;
|
|
464
|
+
const active = absoluteIndex === activeIndex;
|
|
465
|
+
return React.createElement("button", {
|
|
466
|
+
key: agent.slug,
|
|
467
|
+
type: "button",
|
|
468
|
+
className: agent.slug === props.value || active
|
|
469
|
+
? "mcoda-agent-setup__agent-row mcoda-agent-setup__agent-row--selected"
|
|
470
|
+
: "mcoda-agent-setup__agent-row",
|
|
471
|
+
role: "option",
|
|
472
|
+
"aria-selected": agent.slug === props.value,
|
|
473
|
+
style: { minHeight: rowHeight },
|
|
474
|
+
onMouseEnter: () => setActiveIndex(absoluteIndex),
|
|
475
|
+
onClick: () => selectAgent(agent),
|
|
476
|
+
}, React.createElement("span", { className: "mcoda-agent-setup__agent-row-main" }, React.createElement("strong", null, agentTitle(agent)), React.createElement("small", null, agentMetadata(agent)), React.createElement("code", null, agent.slug)), agent.slug === props.value
|
|
477
|
+
? React.createElement("span", { className: "mcoda-agent-setup__selected-mark" }, "Selected")
|
|
478
|
+
: null);
|
|
479
|
+
}), React.createElement("div", { style: { height: windowed.afterHeight } })))
|
|
480
|
+
: null);
|
|
481
|
+
}
|
|
482
|
+
function MetricCard(props) {
|
|
483
|
+
return React.createElement("div", { className: "mcoda-agent-setup__metric" }, React.createElement("span", null, props.label), React.createElement("strong", null, props.value), React.createElement("small", null, props.detail));
|
|
484
|
+
}
|
|
485
|
+
function AgentStatusBadge(props) {
|
|
486
|
+
const kind = props.agent?.managedKind;
|
|
487
|
+
const sourceLabel = kind === "cloud"
|
|
488
|
+
? "Cloud"
|
|
489
|
+
: kind === "self_hosted"
|
|
490
|
+
? "Self-hosted"
|
|
491
|
+
: kind === "worker"
|
|
492
|
+
? "Worker"
|
|
493
|
+
: props.agent
|
|
494
|
+
? "Local"
|
|
495
|
+
: props.slug
|
|
496
|
+
? "Missing"
|
|
497
|
+
: "Unset";
|
|
498
|
+
const className = kind === "cloud"
|
|
499
|
+
? "mcoda-agent-setup__badge mcoda-agent-setup__badge--info"
|
|
500
|
+
: kind === "self_hosted"
|
|
501
|
+
? "mcoda-agent-setup__badge mcoda-agent-setup__badge--success"
|
|
502
|
+
: kind === "worker"
|
|
503
|
+
? "mcoda-agent-setup__badge mcoda-agent-setup__badge--worker"
|
|
504
|
+
: props.agent
|
|
505
|
+
? "mcoda-agent-setup__badge"
|
|
506
|
+
: "mcoda-agent-setup__badge mcoda-agent-setup__badge--warning";
|
|
507
|
+
return React.createElement("span", { className }, sourceLabel);
|
|
508
|
+
}
|
|
509
|
+
function defaultAssignmentSource(preferredSource) {
|
|
510
|
+
if (preferredSource === "self_hosted")
|
|
511
|
+
return "self_hosted";
|
|
512
|
+
if (preferredSource === "worker")
|
|
513
|
+
return "worker";
|
|
514
|
+
return "cloud";
|
|
515
|
+
}
|
|
516
|
+
function firstSelfHostedAgentSlug(servers) {
|
|
517
|
+
return servers.find((server) => server.agents.length > 0)?.agents[0]?.slug ?? null;
|
|
518
|
+
}
|
|
519
|
+
function agentTitle(agent) {
|
|
520
|
+
return agent.displayName ?? prettySlug(agent.slug);
|
|
521
|
+
}
|
|
522
|
+
function agentMetadata(agent) {
|
|
523
|
+
return ([
|
|
524
|
+
agent.defaultModel ?? agent.model,
|
|
525
|
+
agent.provider,
|
|
526
|
+
agent.healthStatus && agent.healthStatus !== "-" ? agent.healthStatus : null,
|
|
527
|
+
]
|
|
528
|
+
.filter(Boolean)
|
|
529
|
+
.join(" / ") || agent.slug);
|
|
530
|
+
}
|
|
531
|
+
function prettySlug(slug) {
|
|
532
|
+
return slug
|
|
533
|
+
.replace(/^mswarm-cloud-/, "")
|
|
534
|
+
.replace(/^mswarm-self-hosted-/, "")
|
|
535
|
+
.replace(/^mswarm-worker-/, "")
|
|
536
|
+
.replace(/^mcoda-/, "")
|
|
537
|
+
.replace(/[-_]+/g, " ");
|
|
538
|
+
}
|
|
539
|
+
function formatTimestamp(value) {
|
|
540
|
+
if (!value)
|
|
541
|
+
return "never";
|
|
542
|
+
const parsed = Date.parse(value);
|
|
543
|
+
if (Number.isNaN(parsed))
|
|
544
|
+
return value;
|
|
545
|
+
const deltaMinutes = Math.max(0, Math.round((Date.now() - parsed) / 60000));
|
|
546
|
+
if (deltaMinutes < 1)
|
|
547
|
+
return "just now";
|
|
548
|
+
if (deltaMinutes < 60)
|
|
549
|
+
return `${deltaMinutes}m ago`;
|
|
550
|
+
const deltaHours = Math.round(deltaMinutes / 60);
|
|
551
|
+
if (deltaHours < 24)
|
|
552
|
+
return `${deltaHours}h ago`;
|
|
553
|
+
return `${Math.round(deltaHours / 24)}d ago`;
|
|
290
554
|
}
|
|
291
555
|
function joinClasses(...values) {
|
|
292
556
|
return values.filter(Boolean).join(" ");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cliRuntime.d.ts","sourceRoot":"","sources":["../../src/server/cliRuntime.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAIV,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,2BAA2B;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAC1C,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAED,wBAAgB,4BAA4B,CAC1C,KAAK,GAAE,2BAAgC,GACtC,mBAAmB,
|
|
1
|
+
{"version":3,"file":"cliRuntime.d.ts","sourceRoot":"","sources":["../../src/server/cliRuntime.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAIV,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,2BAA2B;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAC1C,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAED,wBAAgB,4BAA4B,CAC1C,KAAK,GAAE,2BAAgC,GACtC,mBAAmB,CAwIrB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { normalizeAgentCatalogEntries } from "../headless/normalization.js";
|
|
3
|
-
import { isCloudAgent, isSelfHostedAgent } from "../headless/catalog.js";
|
|
3
|
+
import { isCloudAgent, isSelfHostedAgent, isWorkerAgent } from "../headless/catalog.js";
|
|
4
4
|
export function createCliMcodaRuntimeAdapter(input = {}) {
|
|
5
5
|
const command = input.command ?? "mcoda";
|
|
6
6
|
const run = input.runCommand ??
|
|
@@ -82,6 +82,31 @@ export function createCliMcodaRuntimeAdapter(input = {}) {
|
|
|
82
82
|
});
|
|
83
83
|
return (await listLocal(options)).filter(isSelfHostedAgent);
|
|
84
84
|
},
|
|
85
|
+
async listWorkerAgents(options) {
|
|
86
|
+
return runAgents([
|
|
87
|
+
"workers",
|
|
88
|
+
"list",
|
|
89
|
+
"--json",
|
|
90
|
+
...(options?.includeUnreachable ? ["--include-disabled"] : []),
|
|
91
|
+
], {
|
|
92
|
+
source: "worker_catalog",
|
|
93
|
+
synced: false,
|
|
94
|
+
managedKind: "worker",
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
async syncWorkerAgents(options) {
|
|
98
|
+
await run(command, [
|
|
99
|
+
"workers",
|
|
100
|
+
"sync",
|
|
101
|
+
"--prune",
|
|
102
|
+
"--json",
|
|
103
|
+
...(options?.includeUnreachable ? ["--include-disabled"] : []),
|
|
104
|
+
], {
|
|
105
|
+
cwd: input.cwd,
|
|
106
|
+
timeoutMs: input.timeoutMs,
|
|
107
|
+
});
|
|
108
|
+
return (await listLocal(options)).filter(isWorkerAgent);
|
|
109
|
+
},
|
|
85
110
|
listLocalAgents: listLocal,
|
|
86
111
|
};
|
|
87
112
|
}
|
|
@@ -2,6 +2,7 @@ import type { McodaAgentCatalogEntry, McodaRuntimeAdapter } from "../types.js";
|
|
|
2
2
|
export interface InMemoryMcodaRuntimeAdapterInput {
|
|
3
3
|
cloudAgents?: McodaAgentCatalogEntry[];
|
|
4
4
|
selfHostedAgents?: McodaAgentCatalogEntry[];
|
|
5
|
+
workerAgents?: McodaAgentCatalogEntry[];
|
|
5
6
|
localAgents?: McodaAgentCatalogEntry[];
|
|
6
7
|
}
|
|
7
8
|
export declare function createInMemoryMcodaRuntimeAdapter(input?: InMemoryMcodaRuntimeAdapterInput): McodaRuntimeAdapter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inMemoryRuntime.d.ts","sourceRoot":"","sources":["../../src/server/inMemoryRuntime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"inMemoryRuntime.d.ts","sourceRoot":"","sources":["../../src/server/inMemoryRuntime.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,sBAAsB,EAItB,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,gCAAgC;IAC/C,WAAW,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACvC,gBAAgB,CAAC,EAAE,sBAAsB,EAAE,CAAC;IAC5C,YAAY,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACxC,WAAW,CAAC,EAAE,sBAAsB,EAAE,CAAC;CACxC;AAED,wBAAgB,iCAAiC,CAC/C,KAAK,GAAE,gCAAqC,GAC3C,mBAAmB,CA+GrB"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { syncedCloudSlug, syncedSelfHostedSlug, } from "../headless/catalog.js";
|
|
1
|
+
import { syncedCloudSlug, syncedSelfHostedSlug, syncedWorkerSlug, } from "../headless/catalog.js";
|
|
2
2
|
export function createInMemoryMcodaRuntimeAdapter(input = {}) {
|
|
3
3
|
let cloudAgents = input.cloudAgents?.map((agent) => ({ ...agent })) ?? [];
|
|
4
4
|
let selfHostedAgents = input.selfHostedAgents?.map((agent) => ({ ...agent })) ?? [];
|
|
5
|
+
let workerAgents = input.workerAgents?.map((agent) => ({ ...agent })) ?? [];
|
|
5
6
|
let localAgents = input.localAgents?.map((agent) => ({ ...agent })) ?? [];
|
|
6
7
|
let configuredApiKey = false;
|
|
7
8
|
const syncRemote = (remoteAgents, slugFor, managedKind) => {
|
|
@@ -65,6 +66,15 @@ export function createInMemoryMcodaRuntimeAdapter(input = {}) {
|
|
|
65
66
|
}
|
|
66
67
|
return syncRemote(filterProvider(selfHostedAgents, options), syncedSelfHostedSlug, "self_hosted");
|
|
67
68
|
},
|
|
69
|
+
async listWorkerAgents(options) {
|
|
70
|
+
return filterProvider(workerAgents, options).map((agent) => ({ ...agent }));
|
|
71
|
+
},
|
|
72
|
+
async syncWorkerAgents(options) {
|
|
73
|
+
if (!configuredApiKey) {
|
|
74
|
+
throw new Error("mswarm api key is required");
|
|
75
|
+
}
|
|
76
|
+
return syncRemote(filterProvider(workerAgents, options), syncedWorkerSlug, "worker");
|
|
77
|
+
},
|
|
68
78
|
async listLocalAgents(options) {
|
|
69
79
|
return filterProvider(localAgents, options).map((agent) => ({ ...agent }));
|
|
70
80
|
},
|