@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.
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/resolvers/[integrationId]/route.js +157 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/env-status/route.js +5 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +33 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/resolvers/route.js +86 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryCreationCockpit.jsx +30 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryReviewModal.jsx +2 -2
- 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/DataModelShell.jsx +400 -188
- 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/OrchestrationGraphEmptyCanvas.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxOrchestrationEditorPanel.jsx +1 -1
- 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 +14 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-registry-creation-flow.js +24 -19
- 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-graph.js +7 -82
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/resolver-constructor.js +217 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-resolver-registry.js +99 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/unified-resolver-registry.js +545 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-patch-policy.js +2 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-resolver-proposal.js +30 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +69 -0
- package/package.json +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryActionCard.jsx +0 -141
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolConfirmModal.jsx +0 -64
- 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;
|