@growthub/cli 0.14.2 → 0.14.4

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 (31) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/SKILL.md +4 -2
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/agent-outcomes/route.js +85 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/apps/route.js +187 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +69 -1
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/patch/preflight/route.js +152 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +21 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +88 -1
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +72 -1
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/swarm-condition/route.js +2 -2
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-source/route.js +21 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflow/publish/route.js +338 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensPanel.jsx +1 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/CeoCockpit.jsx +532 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +36 -5
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +9 -1
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +11 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +22 -165
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-agent-teams.js +211 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-bootstrap-console.js +325 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-cockpit-console.js +206 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-publish.js +179 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +89 -5
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-app-registry.js +539 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-config.js +11 -2
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +23 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-outcome-receipts.js +157 -0
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-patch-policy.js +402 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +69 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +10 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/skills/governed-workspace-mutation/SKILL.md +203 -0
  31. package/package.json +2 -2
@@ -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;
@@ -48,6 +48,7 @@ import {
48
48
  WorkspaceHelperSetupModal,
49
49
  } from "../../components/WorkspaceHelperSetupModal.jsx";
50
50
  import { SwarmRunCockpit, SwarmAgentTranscript } from "./SwarmRunCockpit.jsx";
51
+ import { CeoCockpit } from "./CeoCockpit.jsx";
51
52
  import { SidecarExpandView } from "./SidecarExpandView.jsx";
52
53
  import { parseSlashInput } from "./helper-commands.js";
53
54
  import {
@@ -975,6 +976,10 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
975
976
  };
976
977
 
977
978
  const inSwarmView = activeView === "swarm-list" || activeView === "swarm-detail" || activeView === "tool-output";
979
+ // CEO cockpit shares the same sidecar shell as the swarm views (read-only
980
+ // oversight surface). Kept separate from inSwarmView so the swarm cockpit's
981
+ // run machinery and header affordances are untouched.
982
+ const inCeoView = activeView === "ceo";
978
983
  const canOpenSwarmWorkflow = Boolean(
979
984
  inSwarmView
980
985
  && activeTab === "assistant"
@@ -1009,7 +1014,7 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
1009
1014
  {/* Header — title left; gear toggles Assistant ↔ Setup, then close. */}
1010
1015
  <div className="dm-sidecar-header">
1011
1016
  <div className="dm-sidecar-header-left">
1012
- {inSwarmView && (
1017
+ {(inSwarmView || inCeoView) && (
1013
1018
  <button
1014
1019
  type="button"
1015
1020
  className="dm-sidecar-icon-btn"
@@ -1028,9 +1033,11 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
1028
1033
  <span className="dm-sidecar-title" data-helper-title="">
1029
1034
  {inSwarmView
1030
1035
  ? "Background tasks"
1031
- : threadActive
1032
- ? deriveThreadDisplayTitle(initialThread, "Workspace Helper")
1033
- : "Workspace Helper"}
1036
+ : inCeoView
1037
+ ? "CEO Cockpit"
1038
+ : threadActive
1039
+ ? deriveThreadDisplayTitle(initialThread, "Workspace Helper")
1040
+ : "Workspace Helper"}
1034
1041
  </span>
1035
1042
  </div>
1036
1043
  <div className="dm-sidecar-header-right">
@@ -1086,11 +1093,35 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
1086
1093
  </div>
1087
1094
  )}
1088
1095
 
1096
+ {/* CEO cockpit view — the chief-orchestrator oversight surface. Same
1097
+ sidecar shell, read-only: it derives the swarm fleet and hands every
1098
+ "Open" back to the swarm-detail surface (handleOpenArtifact) — no new
1099
+ route, no execution, no config mutation. */}
1100
+ {activeTab === "assistant" && inCeoView && (
1101
+ <div className="dm-sidecar-body dm-swarm-body" data-ceo-view={activeView}>
1102
+ <CeoCockpit
1103
+ workspaceConfig={workspaceConfig}
1104
+ onOpenArtifact={(artifact) => { if (artifact) handleOpenArtifact(artifact); }}
1105
+ onConfigRefresh={refreshWorkspaceConfig}
1106
+ onSeedSwarm={(seedPrompt) => {
1107
+ setActiveView("chat");
1108
+ onPickIntent("swarm");
1109
+ setPrompt(
1110
+ typeof seedPrompt === "string" && seedPrompt.trim()
1111
+ ? `${seedPrompt.trim()} `
1112
+ : "Propose a governed agent swarm: "
1113
+ );
1114
+ }}
1115
+ onOpenSetup={() => setActiveTab("setup")}
1116
+ />
1117
+ </div>
1118
+ )}
1119
+
1089
1120
  {/* Assistant tab — composer-at-bottom layout (Twenty Ask AI parity):
1090
1121
  conversation/result area on top (flex:1), bottom-anchored composer
1091
1122
  holds chip stack (empty state) → mode row (active thread) →
1092
1123
  textarea with attach + mode + send-arrow action row. */}
1093
- {activeTab === "assistant" && !inSwarmView && (
1124
+ {activeTab === "assistant" && !inSwarmView && !inCeoView && (
1094
1125
  <div className="dm-sidecar-body dm-helper-body">
1095
1126
  <div className="dm-helper-conversation" ref={conversationRef}>
1096
1127
  {/* Conversation — ChatGPT-grade multi-turn. User bubble
@@ -43,12 +43,20 @@ export const HELPER_COMMANDS = [
43
43
  {
44
44
  name: "/swarm",
45
45
  label: "Swarm",
46
- description: "Propose a governed agent swarm — you review and apply before any run",
46
+ description: "Propose a governed agent swarm — review and apply before any run (or start from an Agent Team blueprint in /ceo)",
47
47
  scope: "swarm",
48
48
  mutates: true,
49
49
  intent: "swarm",
50
50
  promptTemplate: "Propose a governed agent swarm:"
51
51
  },
52
+ {
53
+ name: "/ceo",
54
+ label: "CEO",
55
+ description: "Open the CEO cockpit — fleet oversight, reusable Agent Teams, and your next move (read-only)",
56
+ scope: "workspace",
57
+ mutates: false,
58
+ view: "ceo"
59
+ },
52
60
  {
53
61
  name: "/register-api",
54
62
  label: "Register API",
@@ -9211,6 +9211,14 @@ body.workspace-rail-collapsed .workspace-builder.workspace-lens-page,
9211
9211
  .dm-swarm-body { display: flex; flex-direction: column; min-height: 0; }
9212
9212
  .dm-swarm-cockpit { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 14px 16px; display: flex; flex-direction: column; gap: 10px; }
9213
9213
  .dm-swarm-cockpit-list { display: flex; flex-direction: column; gap: 8px; }
9214
+ .dm-ceo-tabs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px; margin: -4px 0 2px; }
9215
+ .dm-ceo-tabs button { min-height: 30px; padding: 5px 10px; border: 1px solid #e5e7eb; border-radius: 5px; background: #fff; color: #374151; font-size: 12px; font-weight: 600; cursor: pointer; }
9216
+ .dm-ceo-tabs button:hover { border-color: #cbd5e1; color: #111827; }
9217
+ .dm-ceo-tabs button.is-active { background: #111827; border-color: #111827; color: #fff; }
9218
+ /* CEO fleet list — layout-only grouping; the parent .dm-swarm-cockpit owns the
9219
+ scroll, so this never introduces a second scrollbar. Visible count is capped
9220
+ in the component (CEO_FLEET_VISIBLE_CAP). */
9221
+ .dm-ceo-report-list { display: grid; gap: 8px; }
9214
9222
  .dm-swarm-section-row { display: flex; align-items: center; justify-content: space-between; margin-top: 4px; }
9215
9223
 
9216
9224
  /* Run card — surface chrome comes from dm-helper-toolcall in the JSX. */
@@ -9218,6 +9226,9 @@ body.workspace-rail-collapsed .workspace-builder.workspace-lens-page,
9218
9226
  .dm-swarm-card-head { display: flex; align-items: center; gap: 8px; }
9219
9227
  .dm-swarm-card-title { flex: 1; }
9220
9228
  .dm-swarm-card-action { height: 24px; padding: 0 7px; }
9229
+ .dm-ceo-card-redirect { opacity: 0; transition: opacity .12s ease, border-color .12s ease, color .12s ease; }
9230
+ .dm-swarm-card:hover .dm-ceo-card-redirect,
9231
+ .dm-swarm-card:focus-within .dm-ceo-card-redirect { opacity: 1; }
9221
9232
  .dm-swarm-card-meta { display: flex; align-items: center; gap: 10px; }
9222
9233
  .dm-swarm-card-meta .dm-run-console__hint { font-size: 12px; }
9223
9234
  .dm-swarm-card-kind { font-weight: 600; }