@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.
@@ -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("button", { type: "button", disabled: busy, onClick: refresh }, "Refresh")), error
92
- ? React.createElement("p", { className: "mcoda-agent-setup__error" }, error)
93
- : null, snapshot
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("p", null, busy ? "Loading" : "No snapshot loaded"), React.createElement(components.MswarmAccessCard, {
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("div", { className: "mcoda-agent-setup__summary" }, React.createElement("div", null, `Provider: ${snapshot.provider}`), React.createElement("div", null, `mswarm Key: ${snapshot.mswarmApiKeyConfigured
119
- ? `configured${snapshot.mswarmApiKeyLast4 ? ` (${snapshot.mswarmApiKeyLast4})` : ""}`
120
- : "missing"}`), React.createElement("div", null, `Synced Agents: ${snapshot.catalog.localAgents.length}`), React.createElement("div", null, `Cloud Agents: ${snapshot.catalog.cloudAgents.length}`), React.createElement("div", null, `Self-hosted Servers: ${snapshot.catalog.selfHostedServers.length}`), warningEntries.length
121
- ? React.createElement("ul", { className: "mcoda-agent-setup__warnings" }, warningEntries.map(([key, value]) => React.createElement("li", { key }, `${key}: ${value}`)))
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("input", {
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
- : "mswarm API key",
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", { type: "button", disabled: props.busy, onClick: props.onSyncAgents }, labels.syncAgents));
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) => ({ ...nextSources, ...current }));
165
- setServers((current) => ({ ...nextServers, ...current }));
166
- }, [props.assignments, props.snapshot.catalog.cloudAgents, props.snapshot.catalog.selfHostedServers, props.stages]);
167
- return React.createElement("section", { className: "mcoda-agent-setup__assignments" }, React.createElement("h2", null, "Stage Assignments"), React.createElement("div", { className: "mcoda-agent-setup__stage-list" }, props.stages.map((stage) => {
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 === "self_hosted" ? "self_hosted" : "cloud");
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
- : server?.agents ?? [];
175
- return React.createElement("div", { className: "mcoda-agent-setup__stage-row", key: stage.stageKey }, React.createElement("div", null, React.createElement("strong", null, stage.displayName), stage.description
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: (value) => setSources((current) => ({ ...current, [stage.stageKey]: value })),
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: serverId ?? "",
185
- onChange: (value) => setServers((current) => ({ ...current, [stage.stageKey]: value })),
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: props.assignments[stage.stageKey] ?? "",
284
+ value: agentSelectValue,
190
285
  onChange: (slug) => props.onAssignmentChange(stage.stageKey, slug || null),
191
- }));
192
- })), React.createElement("button", {
193
- type: "button",
194
- disabled: props.busy,
195
- onClick: props.onSaveAssignments,
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("select", {
201
- value: props.value,
202
- onChange: (event) => props.onChange(event.target.value === "self_hosted" ? "self_hosted" : "cloud"),
203
- }, React.createElement("option", { value: "cloud" }, labels.cloud), React.createElement("option", { value: "self_hosted" }, labels.selfHosted));
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 ?? 40;
231
- const viewportHeight = props.viewportHeight ?? 240;
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
- props.onChange(filtered[activeIndex].slug);
424
+ selectAgent(filtered[activeIndex]);
259
425
  }
260
426
  };
261
- return React.createElement("div", { className: "mcoda-agent-setup__agent-search", onKeyDown }, React.createElement("input", {
262
- value: query,
263
- onChange: (event) => setQuery(event.target.value),
264
- "aria-label": "Search agents",
265
- placeholder: "Search agent, model, provider, or slug",
266
- }), selected
267
- ? React.createElement("div", { className: "mcoda-agent-setup__selected-agent" }, selected.displayName ?? selected.slug)
268
- : null, React.createElement("div", null, `Showing all ${filtered.length} agents`), React.createElement("div", {
269
- className: "mcoda-agent-setup__agent-list",
270
- role: "listbox",
271
- style: { maxHeight: viewportHeight, overflowY: "auto" },
272
- onScroll: (event) => setScrollTop(event.currentTarget.scrollTop),
273
- }, React.createElement("div", { style: { height: windowed.beforeHeight } }), windowed.items.map((agent) => {
274
- const absoluteIndex = filtered.findIndex((candidate) => candidate.slug === agent.slug);
275
- const active = absoluteIndex === activeIndex;
276
- return (React.createElement("button", {
277
- key: agent.slug,
278
- type: "button",
279
- className: agent.slug === props.value || active
280
- ? "mcoda-agent-setup__agent-row mcoda-agent-setup__agent-row--selected"
281
- : "mcoda-agent-setup__agent-row",
282
- role: "option",
283
- "aria-selected": agent.slug === props.value,
284
- style: { minHeight: rowHeight },
285
- onClick: () => props.onChange(agent.slug),
286
- }, React.createElement("span", null, agent.displayName ?? agent.slug), React.createElement("small", null, [agent.model ?? agent.defaultModel, agent.provider, agent.healthStatus]
287
- .filter(Boolean)
288
- .join(" / ")), React.createElement("code", null, agent.slug)));
289
- }), React.createElement("div", { style: { height: windowed.afterHeight } })));
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,CA4GrB"}
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":"AAIA,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,WAAW,CAAC,EAAE,sBAAsB,EAAE,CAAC;CACxC;AAED,wBAAgB,iCAAiC,CAC/C,KAAK,GAAE,gCAAqC,GAC3C,mBAAmB,CAqGrB"}
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
  },