@growthub/cli 0.14.3 → 0.14.5

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.
Files changed (29) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/resolvers/[integrationId]/route.js +157 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/env-status/route.js +5 -1
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +33 -1
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/resolvers/route.js +86 -4
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryCreationCockpit.jsx +30 -5
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryReviewModal.jsx +2 -2
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/CeoCockpit.jsx +532 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +400 -188
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +36 -5
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +1 -1
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +1 -1
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxOrchestrationEditorPanel.jsx +1 -1
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +9 -1
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +14 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-registry-creation-flow.js +24 -19
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-agent-teams.js +211 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-bootstrap-console.js +325 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-cockpit-console.js +206 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +7 -82
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/resolver-constructor.js +217 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-resolver-registry.js +99 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/unified-resolver-registry.js +545 -0
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-patch-policy.js +2 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-resolver-proposal.js +30 -2
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +69 -0
  26. package/package.json +2 -2
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryActionCard.jsx +0 -141
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolConfirmModal.jsx +0 -64
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolDraftPanel.jsx +0 -376
@@ -0,0 +1,532 @@
1
+ "use client";
2
+
3
+ /**
4
+ * CeoCockpit — the governed "chief orchestrator" surface inside the Workspace
5
+ * Helper sidecar (GOVERNED_COCKPIT_ENTRY_POINT_PATTERN_V1 +
6
+ * CEO_PRIMITIVE_COCKPIT_ROADMAP_V1).
7
+ *
8
+ * Two state-derived modes, one /ceo view:
9
+ *
10
+ * - bootstrap — a first-use checklist that proves the full CEO loop once
11
+ * (create → test → launch → observe → review → govern → complete), then
12
+ * records a completion marker in workspace CONFIG and disappears forever
13
+ * for that workspace. The only mutation is the governed
14
+ * `ceo.bootstrap.complete` proposal through the existing helper/apply lane.
15
+ * - operational — the fleet oversight cockpit: every swarm workflow as a
16
+ * "direct report" with state, readiness, last outcome, and the single
17
+ * next move. Every "Open" hands off to the EXISTING Background Tasks
18
+ * (swarm-run) surface.
19
+ *
20
+ * Read-only with respect to execution: it never runs anything, never mutates
21
+ * config except through helper/apply, invents no telemetry (unreported counts
22
+ * render "—"), and adds no route, object type, or visual grammar beyond the
23
+ * existing dm-* primitives.
24
+ */
25
+
26
+ import { useCallback, useEffect, useMemo, useState } from "react";
27
+ // Inherited icon grammar — same set the swarm cockpit uses.
28
+ import { ArrowUpRight } from "lucide-react";
29
+ import { deriveCeoCockpit } from "@/lib/ceo-cockpit-console";
30
+ import {
31
+ deriveCeoBootstrapState,
32
+ CEO_BOOTSTRAP_COMPLETE_PROPOSAL_TYPE,
33
+ } from "@/lib/ceo-bootstrap-console";
34
+ import {
35
+ deriveAgentTeamsState,
36
+ buildCreateAgentTeamsProposal,
37
+ summarizeTeam,
38
+ } from "@/lib/ceo-agent-teams";
39
+ import { findSwarmRunRows } from "@/lib/workspace-swarm-proposal";
40
+
41
+ // k-formatting identical to SwarmRunCockpit's truthful display.
42
+ function formatCount(value) {
43
+ if (value == null || !Number.isFinite(Number(value))) return "—";
44
+ const n = Number(value);
45
+ if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
46
+ return String(n);
47
+ }
48
+
49
+ const STATE_LABEL = {
50
+ blocked: "Blocked",
51
+ failing: "Failing",
52
+ "never-run": "Not run yet",
53
+ running: "Running",
54
+ completed: "Completed",
55
+ };
56
+
57
+ // Checklist status → inherited run-console dot variant. No new vocabulary.
58
+ function checklistDotVariant(status) {
59
+ switch (status) {
60
+ case "complete": return "ok";
61
+ case "ready": return "active";
62
+ case "blocked": return "fail";
63
+ case "pending":
64
+ default: return "pending";
65
+ }
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Operational mode — fleet of direct reports
70
+ // ---------------------------------------------------------------------------
71
+
72
+ function CeoReportCard({ report, onOpen, emphasis }) {
73
+ const canOpen = Boolean(report.nextAction?.artifact && typeof onOpen === "function");
74
+ return (
75
+ <div
76
+ className="dm-helper-toolcall dm-swarm-card"
77
+ data-ceo-report={report.name}
78
+ data-ceo-state={report.state}
79
+ data-ceo-emphasis={emphasis ? "true" : "false"}
80
+ >
81
+ <div className="dm-swarm-card-head">
82
+ <span className="dm-run-console__tree-dot" data-variant={report.variant} />
83
+ <span className="dm-helper-toolcall-title dm-swarm-card-title">{report.name}</span>
84
+ {canOpen && (
85
+ <button
86
+ type="button"
87
+ className="dm-btn-ghost dm-swarm-card-action"
88
+ onClick={() => onOpen(report.nextAction.artifact)}
89
+ aria-label={`${report.nextAction.label}: ${report.name}`}
90
+ title={report.nextAction.label}
91
+ >
92
+ <ArrowUpRight size={12} aria-hidden="true" />
93
+ </button>
94
+ )}
95
+ </div>
96
+ <div className="dm-swarm-card-meta">
97
+ <span className="dm-run-console__hint dm-swarm-card-kind">Workflow</span>
98
+ <span className="dm-run-console__hint">{STATE_LABEL[report.state] || report.state}</span>
99
+ </div>
100
+ <div className="dm-swarm-card-meta">
101
+ <span className="dm-run-console__hint">{`${report.agentCount} Agents`}</span>
102
+ <span className="dm-run-console__hint">
103
+ {report.readiness.ready
104
+ ? `${report.readiness.adapter}${report.readiness.agentHost ? ` · ${report.readiness.agentHost}` : ""}`
105
+ : "Execution target needed"}
106
+ </span>
107
+ {report.lastRun && (
108
+ <span className="dm-run-console__hint">{`${formatCount(report.lastRun.totalTokens)} Tokens`}</span>
109
+ )}
110
+ </div>
111
+ <div className="dm-helper-stream dm-swarm-card-desc">{report.headline}</div>
112
+ </div>
113
+ );
114
+ }
115
+
116
+ // Bound the rendered fleet so a workspace with hundreds of workflows stays a
117
+ // tidy, scrollable list rather than an unbounded wall of cards. The attention
118
+ // pick is always shown above this; the overflow count points to Background
119
+ // Tasks for the full set. Records are never hidden by name collision — the
120
+ // cap is purely by count and disclosed.
121
+ const CEO_FLEET_VISIBLE_CAP = 50;
122
+
123
+ function CeoFleetView({ model, onOpenArtifact }) {
124
+ const { fleet, attention, reports, governance } = model;
125
+ // Filter by stable reportId, not name — duplicate Names must never drop or
126
+ // merge a record from the fleet.
127
+ const others = attention ? reports.filter((r) => r.reportId !== attention.reportId) : reports;
128
+ const visible = others.slice(0, CEO_FLEET_VISIBLE_CAP);
129
+ const overflow = others.length - visible.length;
130
+ return (
131
+ <>
132
+ <div className="dm-swarm-section-row">
133
+ <span className="dm-run-console__hint">
134
+ {`${fleet.total} workflow${fleet.total === 1 ? "" : "s"} · ${fleet.runnable} runnable · ${fleet.blocked} blocked · ${fleet.failing} failing`}
135
+ </span>
136
+ {governance.blockedAttempts > 0 && (
137
+ <span className="dm-run-console__hint" title="Blocked governance attempts in the outcome stream">
138
+ {`${governance.blockedAttempts} blocked attempt${governance.blockedAttempts === 1 ? "" : "s"}`}
139
+ </span>
140
+ )}
141
+ </div>
142
+
143
+ {reports.length === 0 && (
144
+ <p className="dm-run-console__hint">
145
+ Your Fleet is empty. Define a reusable Agent Team below (or use /swarm
146
+ directly) to propose a governed swarm — once applied, the running
147
+ workflow appears here in History to oversee, launch, and review from
148
+ Background Tasks.
149
+ </p>
150
+ )}
151
+
152
+ {attention && (
153
+ <>
154
+ <span className="dm-field-label">Needs your attention</span>
155
+ <CeoReportCard report={attention} onOpen={onOpenArtifact} emphasis />
156
+ </>
157
+ )}
158
+
159
+ {others.length > 0 && (
160
+ <>
161
+ <span className="dm-run-console__hint">History</span>
162
+ <div className="dm-ceo-report-list" data-ceo-report-list="">
163
+ {visible.map((report) => (
164
+ <CeoReportCard key={report.reportId} report={report} onOpen={onOpenArtifact} />
165
+ ))}
166
+ </div>
167
+ {overflow > 0 && (
168
+ <span className="dm-run-console__hint">
169
+ {`Showing ${visible.length} of ${others.length} workflows — open Background Tasks for the rest.`}
170
+ </span>
171
+ )}
172
+ </>
173
+ )}
174
+ </>
175
+ );
176
+ }
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // Agent Teams — the atomic, reusable configuration layer (not runtime)
180
+ // ---------------------------------------------------------------------------
181
+
182
+ function CeoAgentTeamsSection({ teams, workflows, onCreate, busy, error }) {
183
+ const workflowByName = useMemo(() => {
184
+ const map = new Map();
185
+ for (const entry of workflows || []) {
186
+ const name = String(entry?.row?.Name || "").trim();
187
+ if (name && !map.has(name)) map.set(name, entry);
188
+ }
189
+ return map;
190
+ }, [workflows]);
191
+
192
+ const openLinkedWorkflow = useCallback((team) => {
193
+ const workflowName = String(team?.linkedSwarmWorkflowName || "").trim();
194
+ const entry = workflowName ? workflowByName.get(workflowName) : null;
195
+ if (!entry?.objectId || !entry?.row?.Name) return;
196
+ const params = new URLSearchParams({
197
+ object: entry.objectId,
198
+ row: entry.row.Name,
199
+ field: "orchestrationConfig",
200
+ });
201
+ window.location.assign(`/workflows?${params.toString()}`);
202
+ }, [workflowByName]);
203
+
204
+ return (
205
+ <>
206
+ <div className="dm-swarm-section-row">
207
+ <span className="dm-field-label">Agent Teams</span>
208
+ <span className="dm-run-console__hint">Reusable blueprints — configuration, not runtime</span>
209
+ </div>
210
+
211
+ {error && (
212
+ <div className="dm-helper-error" role="alert">
213
+ <span>{error}</span>
214
+ </div>
215
+ )}
216
+
217
+ {!teams.present ? (
218
+ <div className="dm-helper-toolcall dm-swarm-card" data-ceo-teams="empty">
219
+ <div className="dm-helper-stream dm-swarm-card-desc">
220
+ Save reusable swarm blueprints — orchestrator, sub-agent roles, skills,
221
+ processes, and outcome criteria — then launch them as governed swarms
222
+ with /swarm. Blueprints never run on their own; the run still lands in
223
+ the Fleet (Background Tasks) and emits receipts.
224
+ </div>
225
+ <button type="button" className="dm-btn-ghost" onClick={onCreate} disabled={busy}>
226
+ Create Agent Teams table
227
+ </button>
228
+ </div>
229
+ ) : teams.count === 0 ? (
230
+ <p className="dm-run-console__hint">
231
+ No Agent Teams yet. Add rows to the “Agent Swarm Teams” object in the
232
+ Data Model grid, then launch one as a governed swarm with /swarm.
233
+ </p>
234
+ ) : (
235
+ <div className="dm-ceo-report-list" data-ceo-team-list="">
236
+ {teams.teams.map((team) => (
237
+ <div key={team.teamId} className="dm-helper-toolcall dm-swarm-card" data-ceo-team={team.teamId}>
238
+ <div className="dm-swarm-card-head">
239
+ <span className="dm-run-console__tree-dot" data-variant="pending" />
240
+ <span className="dm-helper-toolcall-title dm-swarm-card-title">{team.name}</span>
241
+ {team.linkedSwarmWorkflowName && workflowByName.has(team.linkedSwarmWorkflowName) && (
242
+ <button
243
+ type="button"
244
+ className="dm-btn-ghost dm-swarm-card-action dm-ceo-card-redirect"
245
+ onClick={() => openLinkedWorkflow(team)}
246
+ title="Open linked workflow canvas"
247
+ aria-label={`Open workflow canvas for ${team.name}`}
248
+ >
249
+ <ArrowUpRight size={12} aria-hidden="true" />
250
+ </button>
251
+ )}
252
+ </div>
253
+ <div className="dm-swarm-card-meta">
254
+ <span className="dm-run-console__hint dm-swarm-card-kind">Blueprint</span>
255
+ <span className="dm-run-console__hint">{summarizeTeam(team) || team.status}</span>
256
+ </div>
257
+ {team.teamPurpose && (
258
+ <div className="dm-helper-stream dm-swarm-card-desc">{team.teamPurpose}</div>
259
+ )}
260
+ </div>
261
+ ))}
262
+ </div>
263
+ )}
264
+ </>
265
+ );
266
+ }
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // Bootstrap mode — first-use closed-loop checklist
270
+ // ---------------------------------------------------------------------------
271
+
272
+ function CeoChecklistRow({ item, onAction, actionBusy }) {
273
+ const action = item.nextAction;
274
+ const actionable = action && (item.status === "ready" || item.status === "blocked");
275
+ return (
276
+ <div className="dm-helper-toolcall dm-swarm-card" data-ceo-step={item.id} data-ceo-status={item.status}>
277
+ <div className="dm-swarm-card-head">
278
+ <span className="dm-run-console__tree-dot" data-variant={checklistDotVariant(item.status)} />
279
+ <span className="dm-helper-toolcall-title dm-swarm-card-title">{item.label}</span>
280
+ {actionable && (
281
+ <button
282
+ type="button"
283
+ className="dm-btn-ghost dm-swarm-card-action"
284
+ onClick={() => onAction(item)}
285
+ disabled={actionBusy}
286
+ aria-label={action.label}
287
+ title={action.label}
288
+ >
289
+ {action.label}
290
+ </button>
291
+ )}
292
+ </div>
293
+ {item.guidance && (
294
+ <div className="dm-helper-stream dm-swarm-card-desc">{item.guidance}</div>
295
+ )}
296
+ </div>
297
+ );
298
+ }
299
+
300
+ function CeoBootstrapView({ model, onAction, actionBusy, error }) {
301
+ const { checklist, progress, primaryAction } = model;
302
+ return (
303
+ <>
304
+ <div className="dm-swarm-section-row">
305
+ <span className="dm-run-console__hint">
306
+ {`Set up the CEO · ${progress.completed}/${progress.total} steps`}
307
+ </span>
308
+ {primaryAction && (
309
+ <span className="dm-run-console__hint" title="Your next move">
310
+ {`Next: ${primaryAction.label}`}
311
+ </span>
312
+ )}
313
+ </div>
314
+
315
+ <div className="dm-helper-stream dm-swarm-card-desc">
316
+ You're operating as the workspace orchestrator (the CEO). Prove the loop
317
+ once — create a swarm, validate it, launch it through Background Tasks,
318
+ observe the result — and this checklist locks in and disappears.
319
+ </div>
320
+
321
+ {error && (
322
+ <div className="dm-helper-error" role="alert">
323
+ <span>{error}</span>
324
+ </div>
325
+ )}
326
+
327
+ {checklist.map((item) => (
328
+ <CeoChecklistRow key={item.id} item={item} onAction={onAction} actionBusy={actionBusy} />
329
+ ))}
330
+ </>
331
+ );
332
+ }
333
+
334
+ // ---------------------------------------------------------------------------
335
+ // Container — derives the mode and wires actions to governed surfaces
336
+ // ---------------------------------------------------------------------------
337
+
338
+ export function CeoCockpit({ workspaceConfig, onOpenArtifact, onConfigRefresh, onSeedSwarm, onOpenSetup }) {
339
+ // Optional governance rollup — read-only, graceful fallback to config-only.
340
+ const [receipts, setReceipts] = useState([]);
341
+ const [activeOperationalTab, setActiveOperationalTab] = useState("history");
342
+ const [actionBusy, setActionBusy] = useState(false);
343
+ const [error, setError] = useState("");
344
+ // Separate error channel for the Agent Teams section so it never collides
345
+ // with the bootstrap completion error (both can be on screen at once).
346
+ const [teamsError, setTeamsError] = useState("");
347
+
348
+ const refreshReceipts = useCallback(async () => {
349
+ try {
350
+ const res = await fetch("/api/workspace/agent-outcomes");
351
+ const data = await res.json();
352
+ setReceipts(Array.isArray(data?.receipts) ? data.receipts : []);
353
+ } catch {
354
+ // Non-fatal — the fleet/bootstrap still derives from config alone.
355
+ }
356
+ }, []);
357
+
358
+ useEffect(() => {
359
+ refreshReceipts();
360
+ }, [refreshReceipts]);
361
+
362
+ const fleetModel = useMemo(
363
+ () => deriveCeoCockpit({ workspaceConfig, receipts }),
364
+ [workspaceConfig, receipts]
365
+ );
366
+ const bootstrapModel = useMemo(
367
+ () => deriveCeoBootstrapState({ workspaceConfig, receipts }),
368
+ [workspaceConfig, receipts]
369
+ );
370
+ const teamsModel = useMemo(
371
+ () => deriveAgentTeamsState({ workspaceConfig }),
372
+ [workspaceConfig]
373
+ );
374
+ const swarmWorkflows = useMemo(
375
+ () => findSwarmRunRows(workspaceConfig),
376
+ [workspaceConfig]
377
+ );
378
+
379
+ const handleOpenArtifact = useCallback(
380
+ (artifact) => {
381
+ if (artifact && typeof onOpenArtifact === "function") onOpenArtifact(artifact);
382
+ },
383
+ [onOpenArtifact]
384
+ );
385
+
386
+ // Mark CEO setup complete — the ONLY mutation, through the governed
387
+ // helper/apply lane. The server refuses unless the loop is provably done.
388
+ const markComplete = useCallback(async () => {
389
+ setActionBusy(true);
390
+ setError("");
391
+ try {
392
+ const res = await fetch("/api/workspace/helper/apply", {
393
+ method: "POST",
394
+ headers: { "content-type": "application/json" },
395
+ body: JSON.stringify({
396
+ proposals: [
397
+ {
398
+ type: CEO_BOOTSTRAP_COMPLETE_PROPOSAL_TYPE,
399
+ affectedField: "dataModel",
400
+ payload: {},
401
+ rationale: "Mark CEO setup complete after proving the swarm loop end to end.",
402
+ },
403
+ ],
404
+ reviewedBy: "user",
405
+ }),
406
+ });
407
+ const data = await res.json();
408
+ const skipped = Array.isArray(data?.skipped) ? data.skipped : [];
409
+ if (data?.ok === false) {
410
+ setError(data?.error || "Could not complete CEO setup.");
411
+ } else if (skipped.length > 0) {
412
+ setError(skipped[0]?.reason || "CEO setup is not ready to complete yet.");
413
+ } else if (typeof onConfigRefresh === "function") {
414
+ onConfigRefresh();
415
+ }
416
+ } catch (err) {
417
+ setError(err?.message || "Apply failed.");
418
+ } finally {
419
+ setActionBusy(false);
420
+ }
421
+ }, [onConfigRefresh]);
422
+
423
+ // Create the governed Agent Teams table through the EXISTING
424
+ // dataModel.object.create helper/apply lane (objectType "custom"). No new
425
+ // object type, no new lane.
426
+ const createAgentTeams = useCallback(async () => {
427
+ setActionBusy(true);
428
+ setTeamsError("");
429
+ try {
430
+ const res = await fetch("/api/workspace/helper/apply", {
431
+ method: "POST",
432
+ headers: { "content-type": "application/json" },
433
+ body: JSON.stringify({ proposals: [buildCreateAgentTeamsProposal()], reviewedBy: "user" }),
434
+ });
435
+ const data = await res.json();
436
+ const skipped = Array.isArray(data?.skipped) ? data.skipped : [];
437
+ if (data?.ok === false) {
438
+ setTeamsError(data?.error || "Could not create the Agent Teams table.");
439
+ } else if (skipped.length > 0) {
440
+ setTeamsError(skipped[0]?.reason || "Agent Teams table could not be created.");
441
+ } else if (typeof onConfigRefresh === "function") {
442
+ onConfigRefresh();
443
+ }
444
+ } catch (err) {
445
+ setTeamsError(err?.message || "Apply failed.");
446
+ } finally {
447
+ setActionBusy(false);
448
+ }
449
+ }, [onConfigRefresh]);
450
+
451
+ const handleChecklistAction = useCallback(
452
+ (item) => {
453
+ const action = item?.nextAction;
454
+ if (!action) return;
455
+ switch (action.kind) {
456
+ case "open":
457
+ handleOpenArtifact(action.artifact);
458
+ break;
459
+ case "seed-swarm":
460
+ if (typeof onSeedSwarm === "function") onSeedSwarm();
461
+ break;
462
+ case "setup":
463
+ if (typeof onOpenSetup === "function") onOpenSetup();
464
+ break;
465
+ case "mark-complete":
466
+ markComplete();
467
+ break;
468
+ default:
469
+ break;
470
+ }
471
+ },
472
+ [handleOpenArtifact, onSeedSwarm, onOpenSetup, markComplete]
473
+ );
474
+
475
+ return (
476
+ <div className="dm-swarm-cockpit" data-ceo-cockpit="" data-ceo-mode={bootstrapModel.mode}>
477
+ {bootstrapModel.mode === "bootstrap" ? (
478
+ <>
479
+ <CeoBootstrapView
480
+ model={bootstrapModel}
481
+ onAction={handleChecklistAction}
482
+ actionBusy={actionBusy}
483
+ error={error}
484
+ />
485
+ <CeoAgentTeamsSection
486
+ teams={teamsModel}
487
+ workflows={swarmWorkflows}
488
+ onCreate={createAgentTeams}
489
+ busy={actionBusy}
490
+ error={teamsError}
491
+ />
492
+ </>
493
+ ) : (
494
+ <>
495
+ <div className="dm-ceo-tabs" role="tablist" aria-label="CEO cockpit sections">
496
+ <button
497
+ type="button"
498
+ role="tab"
499
+ aria-selected={activeOperationalTab === "history"}
500
+ className={activeOperationalTab === "history" ? "is-active" : ""}
501
+ onClick={() => setActiveOperationalTab("history")}
502
+ >
503
+ History
504
+ </button>
505
+ <button
506
+ type="button"
507
+ role="tab"
508
+ aria-selected={activeOperationalTab === "teams"}
509
+ className={activeOperationalTab === "teams" ? "is-active" : ""}
510
+ onClick={() => setActiveOperationalTab("teams")}
511
+ >
512
+ Agent Teams
513
+ </button>
514
+ </div>
515
+ {activeOperationalTab === "history" ? (
516
+ <CeoFleetView model={fleetModel} onOpenArtifact={handleOpenArtifact} />
517
+ ) : (
518
+ <CeoAgentTeamsSection
519
+ teams={teamsModel}
520
+ workflows={swarmWorkflows}
521
+ onCreate={createAgentTeams}
522
+ busy={actionBusy}
523
+ error={teamsError}
524
+ />
525
+ )}
526
+ </>
527
+ )}
528
+ </div>
529
+ );
530
+ }
531
+
532
+ export default CeoCockpit;