@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.
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/SKILL.md +4 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/agent-outcomes/route.js +85 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/apps/route.js +187 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +69 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/patch/preflight/route.js +152 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +21 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +88 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +72 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/swarm-condition/route.js +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-source/route.js +21 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflow/publish/route.js +338 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensPanel.jsx +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/CeoCockpit.jsx +532 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +36 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +9 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +11 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +22 -165
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-agent-teams.js +211 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-bootstrap-console.js +325 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-cockpit-console.js +206 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-publish.js +179 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +89 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-app-registry.js +539 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-config.js +11 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +23 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-outcome-receipts.js +157 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-patch-policy.js +402 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +69 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +10 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/skills/governed-workspace-mutation/SKILL.md +203 -0
- 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
|
-
:
|
|
1032
|
-
?
|
|
1033
|
-
:
|
|
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 —
|
|
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",
|
package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css
CHANGED
|
@@ -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; }
|