@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,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CEO bootstrap projection — the first-use closed-loop harness for the CEO
|
|
3
|
+
* cockpit (CEO_PRIMITIVE_COCKPIT_ROADMAP_V1, follow-up to R1/R2).
|
|
4
|
+
*
|
|
5
|
+
* This is a PURE deriver — no React, no fetch, no fs, no config writes, no
|
|
6
|
+
* localStorage. It derives TWO modes from workspace state alone:
|
|
7
|
+
*
|
|
8
|
+
* - "bootstrap" — shown until the workspace has a CEO completion marker.
|
|
9
|
+
* A state-derived smoke-test checklist that proves every
|
|
10
|
+
* CEO primitive end-to-end (orchestrate → create → test →
|
|
11
|
+
* launch → observe → review → govern → complete).
|
|
12
|
+
* - "operational" — the existing fleet cockpit, after completion.
|
|
13
|
+
*
|
|
14
|
+
* The mode comes from a completion marker that lives in workspace CONFIG (a
|
|
15
|
+
* field on the existing well-known workspace-helper sandbox row), NEVER a
|
|
16
|
+
* browser flag. It introduces no new object type, no new API, no new PATCH
|
|
17
|
+
* field, and no new execution path: the checklist is a projection over the
|
|
18
|
+
* existing swarm fleet + receipt stream, and completion is stamped through the
|
|
19
|
+
* existing helper/apply lane via the `ceo.bootstrap.complete` proposal.
|
|
20
|
+
*
|
|
21
|
+
* Completion is gated on config-provable evidence — a governed swarm workflow
|
|
22
|
+
* that is ready AND has a COMPLETED run on its row — so "done" means the loop
|
|
23
|
+
* actually ran, not that a button was clicked.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { deriveCeoCockpit } from "./ceo-cockpit-console.js";
|
|
27
|
+
import { deriveHelperWidgetCausationState } from "./workspace-swarm-proposal.js";
|
|
28
|
+
|
|
29
|
+
// Well-known anchors — the same constants the swarm/helper lanes use. Kept
|
|
30
|
+
// local (they are module-private upstream) so this stays a pure leaf module.
|
|
31
|
+
export const WORKSPACE_HELPER_SANDBOX_OBJECT_ID = "workspace-helper-sandbox";
|
|
32
|
+
export const WORKSPACE_HELPER_ROW_NAME = "workspace-helper";
|
|
33
|
+
export const CEO_BOOTSTRAP_COMPLETE_PROPOSAL_TYPE = "ceo.bootstrap.complete";
|
|
34
|
+
export const CEO_BOOTSTRAP_MARKER_FIELD = "ceoBootstrapCompletedAt";
|
|
35
|
+
export const CEO_BOOTSTRAP_BY_FIELD = "ceoBootstrapCompletedBy";
|
|
36
|
+
export const CEO_BOOTSTRAP_RECEIPT_FIELD = "ceoBootstrapReceiptId";
|
|
37
|
+
|
|
38
|
+
function clean(value) {
|
|
39
|
+
return typeof value === "string" ? value.trim() : "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Locate the well-known helper row (and its object) for marker read/write.
|
|
43
|
+
function findHelperRowLocation(workspaceConfig) {
|
|
44
|
+
const objects = Array.isArray(workspaceConfig?.dataModel?.objects)
|
|
45
|
+
? workspaceConfig.dataModel.objects
|
|
46
|
+
: [];
|
|
47
|
+
const objectIndex = objects.findIndex((o) => o?.id === WORKSPACE_HELPER_SANDBOX_OBJECT_ID);
|
|
48
|
+
if (objectIndex < 0) return { objectIndex: -1, rowIndex: -1, object: null, row: null };
|
|
49
|
+
const object = objects[objectIndex];
|
|
50
|
+
const rows = Array.isArray(object?.rows) ? object.rows : [];
|
|
51
|
+
let rowIndex = rows.findIndex((r) => clean(r?.Name) === WORKSPACE_HELPER_ROW_NAME);
|
|
52
|
+
if (rowIndex < 0 && rows.length > 0) rowIndex = 0;
|
|
53
|
+
return { objectIndex, rowIndex, object, row: rowIndex >= 0 ? rows[rowIndex] : null };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Read the completion marker from config (the single source of mode truth).
|
|
57
|
+
function readCompletion(workspaceConfig) {
|
|
58
|
+
const { object, row } = findHelperRowLocation(workspaceConfig);
|
|
59
|
+
const at = clean(row?.[CEO_BOOTSTRAP_MARKER_FIELD]);
|
|
60
|
+
if (!row || !at) return null;
|
|
61
|
+
return {
|
|
62
|
+
objectId: object?.id || WORKSPACE_HELPER_SANDBOX_OBJECT_ID,
|
|
63
|
+
rowName: clean(row?.Name) || WORKSPACE_HELPER_ROW_NAME,
|
|
64
|
+
completedAt: at,
|
|
65
|
+
completedBy: clean(row?.[CEO_BOOTSTRAP_BY_FIELD]) || null,
|
|
66
|
+
receiptId: clean(row?.[CEO_BOOTSTRAP_RECEIPT_FIELD]) || null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// A receipt counts as governance evidence when it is an execution proof or a
|
|
71
|
+
// governed apply that succeeded — the same lane vocabulary the cockpit uses.
|
|
72
|
+
function hasGovernanceEvidence(receipts) {
|
|
73
|
+
return (Array.isArray(receipts) ? receipts : []).some((r) => {
|
|
74
|
+
if (!r || typeof r !== "object") return false;
|
|
75
|
+
if (r.outcomeStatus === "blocked") return false;
|
|
76
|
+
return r.lane === "execution-proof" || r.lane === "governed-proposal";
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function item(id, label, status, { guidance = "", evidenceRefs = [], nextAction = null } = {}) {
|
|
81
|
+
return { id, label, status, guidance, evidenceRefs, nextAction };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Choose the workflow the checklist follows end-to-end: prefer one that
|
|
85
|
+
// completed, else one that ran, else one that is ready, else the first.
|
|
86
|
+
function pickFocus(reports) {
|
|
87
|
+
return (
|
|
88
|
+
reports.find((r) => r.state === "completed") ||
|
|
89
|
+
reports.find((r) => r.lastRun) ||
|
|
90
|
+
reports.find((r) => r.readiness?.ready) ||
|
|
91
|
+
reports[0] ||
|
|
92
|
+
null
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function openArtifactAction(report, label) {
|
|
97
|
+
if (!report?.nextAction?.artifact) return null;
|
|
98
|
+
return { kind: "open", label, artifact: report.nextAction.artifact };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Derive the CEO bootstrap state from workspace config (+ optional receipts).
|
|
103
|
+
* Pure. See module header for the return shape.
|
|
104
|
+
*/
|
|
105
|
+
export function deriveCeoBootstrapState({ workspaceConfig, receipts = [] } = {}) {
|
|
106
|
+
const completionRef = readCompletion(workspaceConfig);
|
|
107
|
+
const cockpit = deriveCeoCockpit({ workspaceConfig, receipts });
|
|
108
|
+
const reports = cockpit.reports;
|
|
109
|
+
const helperState = deriveHelperWidgetCausationState(workspaceConfig);
|
|
110
|
+
const helperReady = helperState.ready === true;
|
|
111
|
+
const focus = pickFocus(reports);
|
|
112
|
+
const ran = Boolean(focus?.lastRun);
|
|
113
|
+
const completedRun = focus?.lastRun?.status === "completed";
|
|
114
|
+
const failedRun = focus?.lastRun?.status === "failed";
|
|
115
|
+
const governanceEvidence = hasGovernanceEvidence(receipts) || ran;
|
|
116
|
+
|
|
117
|
+
const checklist = [];
|
|
118
|
+
|
|
119
|
+
// 1 — Mental model (informational primer; satisfied by reading the loop).
|
|
120
|
+
checklist.push(item("mental-model", "Understand the CEO loop", "complete", {
|
|
121
|
+
guidance: "You're the CEO: orchestrate a swarm, validate it, launch it, observe truthful telemetry, then review the outcome.",
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
// 2 — Governed swarm workflow exists (helper must be live to create one).
|
|
125
|
+
if (reports.length > 0) {
|
|
126
|
+
checklist.push(item("swarm-workflow", "Create your first agent swarm", "complete", {
|
|
127
|
+
guidance: "A governed swarm workflow is present in the fleet.",
|
|
128
|
+
evidenceRefs: focus?.name ? [focus.name] : [],
|
|
129
|
+
}));
|
|
130
|
+
} else if (helperReady) {
|
|
131
|
+
checklist.push(item("swarm-workflow", "Create your first agent swarm", "ready", {
|
|
132
|
+
guidance: "Propose your first agent swarm with /swarm — review and apply it to create the governed workflow.",
|
|
133
|
+
nextAction: { kind: "seed-swarm", label: "Propose a swarm" },
|
|
134
|
+
}));
|
|
135
|
+
} else {
|
|
136
|
+
checklist.push(item("swarm-workflow", "Create your first agent swarm", "blocked", {
|
|
137
|
+
guidance: `Bring the workspace helper online first. ${helperState.guidance}`,
|
|
138
|
+
nextAction: { kind: "setup", label: "Open Setup" },
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 3 — Execution readiness (the existing eligibility gate).
|
|
143
|
+
if (!focus) {
|
|
144
|
+
checklist.push(item("readiness", "Validate execution readiness", "pending", {
|
|
145
|
+
guidance: "Create a swarm workflow first.",
|
|
146
|
+
}));
|
|
147
|
+
} else if (focus.readiness.ready) {
|
|
148
|
+
checklist.push(item("readiness", "Validate execution readiness", "complete", {
|
|
149
|
+
guidance: `Execution target ready (${focus.readiness.adapter}${focus.readiness.agentHost ? ` · ${focus.readiness.agentHost}` : ""}).`,
|
|
150
|
+
evidenceRefs: [focus.readiness.adapter].filter(Boolean),
|
|
151
|
+
}));
|
|
152
|
+
} else {
|
|
153
|
+
checklist.push(item("readiness", "Validate execution readiness", "blocked", {
|
|
154
|
+
guidance: focus.readiness.guidance,
|
|
155
|
+
nextAction: openArtifactAction(focus, "Open to fix"),
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 4 — Launch through the existing Background Tasks (sandbox-run).
|
|
160
|
+
if (!focus) {
|
|
161
|
+
checklist.push(item("launch", "Launch through Background Tasks", "pending", {
|
|
162
|
+
guidance: "Create a swarm workflow first.",
|
|
163
|
+
}));
|
|
164
|
+
} else if (ran) {
|
|
165
|
+
checklist.push(item("launch", "Launch through Background Tasks", "complete", {
|
|
166
|
+
guidance: "Launched at least once via the existing sandbox-run executor.",
|
|
167
|
+
evidenceRefs: focus.lastRun.runId ? [focus.lastRun.runId] : [],
|
|
168
|
+
}));
|
|
169
|
+
} else if (focus.readiness.ready) {
|
|
170
|
+
checklist.push(item("launch", "Launch through Background Tasks", "ready", {
|
|
171
|
+
guidance: "Open the workflow and press Run — execution stays in Background Tasks.",
|
|
172
|
+
nextAction: openArtifactAction(focus, "Open to launch"),
|
|
173
|
+
}));
|
|
174
|
+
} else {
|
|
175
|
+
checklist.push(item("launch", "Launch through Background Tasks", "pending", {
|
|
176
|
+
guidance: "Resolve execution readiness first.",
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 5 — Observe truthful telemetry (a persisted run record exists).
|
|
181
|
+
if (ran) {
|
|
182
|
+
checklist.push(item("observe", "Observe truthful telemetry", "complete", {
|
|
183
|
+
guidance: "A run record is persisted — tokens/tools show truthfully (— when the adapter reported nothing).",
|
|
184
|
+
evidenceRefs: focus.lastRun.runId ? [focus.lastRun.runId] : [],
|
|
185
|
+
}));
|
|
186
|
+
} else {
|
|
187
|
+
checklist.push(item("observe", "Observe truthful telemetry", "pending", {
|
|
188
|
+
guidance: "Launch the swarm to produce a run record.",
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 6 — Review the outcome (completed vs failed — failure is not success).
|
|
193
|
+
if (!ran) {
|
|
194
|
+
checklist.push(item("review", "Review the outcome", "pending", {
|
|
195
|
+
guidance: "Launch the swarm first.",
|
|
196
|
+
}));
|
|
197
|
+
} else if (completedRun) {
|
|
198
|
+
checklist.push(item("review", "Review the outcome", "complete", {
|
|
199
|
+
guidance: "The run completed successfully.",
|
|
200
|
+
}));
|
|
201
|
+
} else if (failedRun) {
|
|
202
|
+
checklist.push(item("review", "Review the outcome", "blocked", {
|
|
203
|
+
guidance: "Last run failed — review the transcript and re-run before completing setup.",
|
|
204
|
+
nextAction: openArtifactAction(focus, "Open to review"),
|
|
205
|
+
}));
|
|
206
|
+
} else {
|
|
207
|
+
checklist.push(item("review", "Review the outcome", "pending", {
|
|
208
|
+
guidance: "The run is still in progress — open it to watch.",
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 7 — Confirm governance receipts exist.
|
|
213
|
+
checklist.push(item("governance", "Confirm governance receipts", governanceEvidence ? "complete" : "pending", {
|
|
214
|
+
guidance: governanceEvidence
|
|
215
|
+
? "Governed activity is recorded in the agent-outcomes receipt stream."
|
|
216
|
+
: "Receipts appear once you launch a run or apply a governed proposal.",
|
|
217
|
+
}));
|
|
218
|
+
|
|
219
|
+
// 8 — Mark CEO setup complete (the only mutation; goes through helper/apply).
|
|
220
|
+
const prereqIds = ["swarm-workflow", "readiness", "launch", "review"];
|
|
221
|
+
const prereqsMet = prereqIds.every(
|
|
222
|
+
(id) => checklist.find((c) => c.id === id)?.status === "complete"
|
|
223
|
+
);
|
|
224
|
+
if (completionRef) {
|
|
225
|
+
checklist.push(item("complete", "Mark CEO setup complete", "complete", {
|
|
226
|
+
guidance: "CEO setup is complete for this workspace.",
|
|
227
|
+
evidenceRefs: [completionRef.completedAt],
|
|
228
|
+
}));
|
|
229
|
+
} else if (prereqsMet) {
|
|
230
|
+
checklist.push(item("complete", "Mark CEO setup complete", "ready", {
|
|
231
|
+
guidance: "You've proven the full CEO loop — lock it in. This checklist then disappears for this workspace.",
|
|
232
|
+
nextAction: { kind: "mark-complete", label: "Complete" },
|
|
233
|
+
}));
|
|
234
|
+
} else {
|
|
235
|
+
checklist.push(item("complete", "Mark CEO setup complete", "pending", {
|
|
236
|
+
guidance: "Finish the steps above to unlock completion.",
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// The single next move — first actionable (ready/blocked) item in order.
|
|
241
|
+
const primaryItem = checklist.find(
|
|
242
|
+
(c) => (c.status === "ready" || c.status === "blocked") && c.nextAction
|
|
243
|
+
);
|
|
244
|
+
const primaryAction = primaryItem
|
|
245
|
+
? { itemId: primaryItem.id, ...primaryItem.nextAction }
|
|
246
|
+
: null;
|
|
247
|
+
|
|
248
|
+
const completedCount = checklist.filter((c) => c.status === "complete").length;
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
title: "CEO Cockpit",
|
|
252
|
+
mode: completionRef ? "operational" : "bootstrap",
|
|
253
|
+
completed: Boolean(completionRef),
|
|
254
|
+
completionRef,
|
|
255
|
+
checklist,
|
|
256
|
+
primaryAction,
|
|
257
|
+
progress: { completed: completedCount, total: checklist.length },
|
|
258
|
+
focus: focus ? { objectId: focus.objectId, name: focus.name } : null,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Governed completion builder — stamps the CEO bootstrap marker onto the
|
|
264
|
+
* well-known helper row in `dataModel`. Pure (returns a new config; writes
|
|
265
|
+
* nothing). Used by the helper/apply `ceo.bootstrap.complete` lane.
|
|
266
|
+
*
|
|
267
|
+
* Refuses to stamp unless the bootstrap prerequisites are config-provably met
|
|
268
|
+
* (a ready swarm with a completed run) — so the marker is real evidence, not a
|
|
269
|
+
* client assertion.
|
|
270
|
+
*
|
|
271
|
+
* @returns {{ ok: boolean, config: object, error?: string }}
|
|
272
|
+
*/
|
|
273
|
+
export function buildCeoBootstrapCompletion({
|
|
274
|
+
workspaceConfig,
|
|
275
|
+
completedAt,
|
|
276
|
+
completedBy = "user",
|
|
277
|
+
receiptId = null,
|
|
278
|
+
} = {}) {
|
|
279
|
+
const existing = readCompletion(workspaceConfig);
|
|
280
|
+
if (existing) {
|
|
281
|
+
// Idempotent — already complete, nothing to change.
|
|
282
|
+
return { ok: true, config: workspaceConfig };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const state = deriveCeoBootstrapState({ workspaceConfig });
|
|
286
|
+
const completeItem = state.checklist.find((c) => c.id === "complete");
|
|
287
|
+
if (!completeItem || completeItem.status !== "ready") {
|
|
288
|
+
const blocking = state.checklist
|
|
289
|
+
.filter((c) => ["swarm-workflow", "readiness", "launch", "review"].includes(c.id))
|
|
290
|
+
.filter((c) => c.status !== "complete")
|
|
291
|
+
.map((c) => c.label);
|
|
292
|
+
return {
|
|
293
|
+
ok: false,
|
|
294
|
+
config: workspaceConfig,
|
|
295
|
+
error: `CEO bootstrap prerequisites not met: ${blocking.join("; ") || "incomplete"}`,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const { objectIndex, rowIndex } = findHelperRowLocation(workspaceConfig);
|
|
300
|
+
if (objectIndex < 0 || rowIndex < 0) {
|
|
301
|
+
return { ok: false, config: workspaceConfig, error: "workspace helper sandbox row not found" };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const stampedAt = clean(completedAt) || new Date().toISOString();
|
|
305
|
+
const objects = workspaceConfig.dataModel.objects.map((object, oi) => {
|
|
306
|
+
if (oi !== objectIndex) return object;
|
|
307
|
+
const rows = object.rows.map((row, ri) => {
|
|
308
|
+
if (ri !== rowIndex) return row;
|
|
309
|
+
return {
|
|
310
|
+
...row,
|
|
311
|
+
[CEO_BOOTSTRAP_MARKER_FIELD]: stampedAt,
|
|
312
|
+
[CEO_BOOTSTRAP_BY_FIELD]: clean(completedBy) || "user",
|
|
313
|
+
...(clean(receiptId) ? { [CEO_BOOTSTRAP_RECEIPT_FIELD]: clean(receiptId) } : {}),
|
|
314
|
+
};
|
|
315
|
+
});
|
|
316
|
+
return { ...object, rows };
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
ok: true,
|
|
321
|
+
config: { ...workspaceConfig, dataModel: { ...workspaceConfig.dataModel, objects } },
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export default deriveCeoBootstrapState;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CEO cockpit projection — the governed "chief orchestrator" oversight lens
|
|
3
|
+
* over the existing agent-swarm fleet (GOVERNED_COCKPIT_ENTRY_POINT_PATTERN_V1
|
|
4
|
+
* + CEO_PRIMITIVE_COCKPIT_ROADMAP_V1).
|
|
5
|
+
*
|
|
6
|
+
* This is a PURE deriver — no React, no fetch, no fs, no config writes, no
|
|
7
|
+
* localStorage, no CSS. It takes the workspace config (and, optionally, the
|
|
8
|
+
* agent-outcomes receipt stream) and emits a low-entropy view-model the
|
|
9
|
+
* CeoCockpit component renders. It introduces NO new governed object, NO new
|
|
10
|
+
* API, NO new PATCH field: the CEO oversees the same `sandbox-environment`
|
|
11
|
+
* swarm-workflows the Background Tasks cockpit executes, and every "Open"
|
|
12
|
+
* routes back into that existing surface.
|
|
13
|
+
*
|
|
14
|
+
* Causation ITT shape (state -> eligibility -> guidance -> action): each swarm
|
|
15
|
+
* workflow is a "direct report"; its run evidence + execution eligibility
|
|
16
|
+
* derive a state, a human headline, and the single next action — and the fleet
|
|
17
|
+
* rolls those up into one "needs your attention" pick so the CEO always knows
|
|
18
|
+
* the next move without reading logs.
|
|
19
|
+
*
|
|
20
|
+
* Data sources (all already in the contract):
|
|
21
|
+
* - findSwarmRunRows(config) — the governed fleet
|
|
22
|
+
* - deriveSwarmWorkflowExecutionEligibility(entry) — the existing readiness gate
|
|
23
|
+
* - deriveSwarmRunProjection(row.lastResponse) — the existing run projection
|
|
24
|
+
* - workspace:agent-outcomes receipts (optional) — governance rollup only
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { deriveSwarmRunProjection } from "./orchestration-run-console.js";
|
|
28
|
+
import {
|
|
29
|
+
findSwarmRunRows,
|
|
30
|
+
deriveSwarmWorkflowExecutionEligibility,
|
|
31
|
+
} from "./workspace-swarm-proposal.js";
|
|
32
|
+
|
|
33
|
+
// Parse the latest persisted run record off a swarm row. Mirrors the
|
|
34
|
+
// SwarmRunCockpit fallback exactly — row.lastResponse is the durable record
|
|
35
|
+
// when run history has not been re-fetched.
|
|
36
|
+
function parseRowRecord(row) {
|
|
37
|
+
const raw = row?.lastResponse;
|
|
38
|
+
if (!raw) return null;
|
|
39
|
+
if (typeof raw === "object") return raw;
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(String(raw));
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Map a report state to the inherited run-console dot variant grammar
|
|
48
|
+
// (ok | fail | active | pending | canceled) — no new visual vocabulary.
|
|
49
|
+
function variantForState(state) {
|
|
50
|
+
switch (state) {
|
|
51
|
+
case "completed": return "ok";
|
|
52
|
+
case "failing": return "fail";
|
|
53
|
+
case "running": return "active";
|
|
54
|
+
case "blocked": return "canceled";
|
|
55
|
+
case "never-run":
|
|
56
|
+
default: return "pending";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Next-action label per state — the CEO's verb, never a hidden flag.
|
|
61
|
+
function nextActionLabel(state) {
|
|
62
|
+
switch (state) {
|
|
63
|
+
case "blocked": return "Fix execution target";
|
|
64
|
+
case "failing": return "Review failed run";
|
|
65
|
+
case "never-run": return "Open to launch";
|
|
66
|
+
case "running": return "Open running task";
|
|
67
|
+
case "completed":
|
|
68
|
+
default: return "Open task";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Classify one swarm workflow into exactly one report state. Blocked
|
|
73
|
+
// (no runnable execution target) takes priority because nothing else is
|
|
74
|
+
// actionable until it clears — the same truth the Play gate enforces.
|
|
75
|
+
function classifyReport(eligibility, projection) {
|
|
76
|
+
if (!eligibility?.ready) return "blocked";
|
|
77
|
+
const status = projection?.status;
|
|
78
|
+
if (status === "failed") return "failing";
|
|
79
|
+
if (status === "running" || status === "executing") return "running";
|
|
80
|
+
if (status === "completed") return "completed";
|
|
81
|
+
return "never-run";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Human one-liner for a report card — plain language, no jargon.
|
|
85
|
+
function headlineForState(state, eligibility, agentCount) {
|
|
86
|
+
switch (state) {
|
|
87
|
+
case "blocked":
|
|
88
|
+
return eligibility?.guidance || "Set an execution target before this can run.";
|
|
89
|
+
case "failing":
|
|
90
|
+
return "Last run failed — review the transcript and re-run.";
|
|
91
|
+
case "running":
|
|
92
|
+
return "Running now — open to watch progress.";
|
|
93
|
+
case "completed":
|
|
94
|
+
return `Completed · ${agentCount} agent${agentCount === 1 ? "" : "s"}.`;
|
|
95
|
+
case "never-run":
|
|
96
|
+
default:
|
|
97
|
+
return `Ready · ${agentCount} agent${agentCount === 1 ? "" : "s"} · not run yet.`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Attention priority: a CEO looks at what is broken first, then what is
|
|
102
|
+
// blocked, then what has never shipped. Healthy fleets surface nothing.
|
|
103
|
+
const ATTENTION_PRIORITY = ["failing", "blocked", "never-run"];
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build the CEO cockpit view-model.
|
|
107
|
+
*
|
|
108
|
+
* @param {object} args
|
|
109
|
+
* @param {object} args.workspaceConfig live workspace config (GET /api/workspace)
|
|
110
|
+
* @param {Array} [args.receipts] workspace:agent-outcomes stream (optional)
|
|
111
|
+
* @returns {object} view-model — see module header.
|
|
112
|
+
*/
|
|
113
|
+
export function deriveCeoCockpit({ workspaceConfig, receipts = [] } = {}) {
|
|
114
|
+
const entries = findSwarmRunRows(workspaceConfig);
|
|
115
|
+
const safeReceipts = Array.isArray(receipts) ? receipts : [];
|
|
116
|
+
|
|
117
|
+
const reports = entries.map((entry, index) => {
|
|
118
|
+
const eligibility = deriveSwarmWorkflowExecutionEligibility(entry);
|
|
119
|
+
const record = parseRowRecord(entry.row);
|
|
120
|
+
const projection = record ? deriveSwarmRunProjection(record) : null;
|
|
121
|
+
const state = classifyReport(eligibility, projection);
|
|
122
|
+
const agentCount = Number.isFinite(Number(projection?.agentCount))
|
|
123
|
+
? Number(projection.agentCount)
|
|
124
|
+
: Number(eligibility?.runnableNodeCount) || 0;
|
|
125
|
+
const name = String(entry.row?.Name || "").trim();
|
|
126
|
+
// Stable, collision-proof identity: object + row id (or Name) + index.
|
|
127
|
+
// Two workflows that share a Name still get distinct reportIds, so the
|
|
128
|
+
// attention filter and React keys never drop or merge a record.
|
|
129
|
+
const rowKey = String(entry.row?.id || name || `row-${index}`).trim();
|
|
130
|
+
const reportId = `${entry.objectId}::${rowKey}::${index}`;
|
|
131
|
+
const lastRun = projection
|
|
132
|
+
? {
|
|
133
|
+
status: projection.status,
|
|
134
|
+
runId: projection.runId || null,
|
|
135
|
+
totalTokens: projection.totalTokens ?? null,
|
|
136
|
+
totalTools: projection.totalTools ?? null,
|
|
137
|
+
elapsedMs: projection.elapsedMs ?? null,
|
|
138
|
+
}
|
|
139
|
+
: null;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
reportId,
|
|
143
|
+
objectId: entry.objectId,
|
|
144
|
+
name,
|
|
145
|
+
objectLabel: entry.objectLabel || null,
|
|
146
|
+
lifecycleStatus: String(entry.row?.lifecycleStatus || "draft"),
|
|
147
|
+
version: String(entry.row?.version || "1"),
|
|
148
|
+
agentCount,
|
|
149
|
+
state,
|
|
150
|
+
variant: variantForState(state),
|
|
151
|
+
readiness: {
|
|
152
|
+
ready: eligibility.ready,
|
|
153
|
+
status: eligibility.status,
|
|
154
|
+
missing: eligibility.missing,
|
|
155
|
+
guidance: eligibility.guidance,
|
|
156
|
+
adapter: eligibility.adapter,
|
|
157
|
+
agentHost: eligibility.agentHost,
|
|
158
|
+
},
|
|
159
|
+
lastRun,
|
|
160
|
+
headline: headlineForState(state, eligibility, agentCount),
|
|
161
|
+
nextAction: {
|
|
162
|
+
label: nextActionLabel(state),
|
|
163
|
+
// Routes through the EXISTING swarm-run artifact surface — the CEO
|
|
164
|
+
// cockpit never executes; it hands off to Background Tasks.
|
|
165
|
+
artifact: name ? { surface: "swarm-run", objectId: entry.objectId, name } : null,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const countOf = (state) => reports.filter((r) => r.state === state).length;
|
|
171
|
+
const fleet = {
|
|
172
|
+
total: reports.length,
|
|
173
|
+
runnable: reports.filter((r) => r.readiness.ready).length,
|
|
174
|
+
blocked: countOf("blocked"),
|
|
175
|
+
failing: countOf("failing"),
|
|
176
|
+
neverRun: countOf("never-run"),
|
|
177
|
+
running: countOf("running"),
|
|
178
|
+
completed: countOf("completed"),
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// The single next move for the CEO — the highest-priority report, or null
|
|
182
|
+
// when the fleet is healthy. This is the causation "next action".
|
|
183
|
+
let attention = null;
|
|
184
|
+
for (const state of ATTENTION_PRIORITY) {
|
|
185
|
+
const hit = reports.find((r) => r.state === state);
|
|
186
|
+
if (hit) { attention = hit; break; }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Governance rollup is a pure read over the receipt stream — blocked
|
|
190
|
+
// mutation/execution attempts the CEO should be aware of. Zeroed when no
|
|
191
|
+
// receipts were supplied (the cockpit still renders the fleet).
|
|
192
|
+
const blockedAttempts = safeReceipts.filter(
|
|
193
|
+
(r) => r && r.outcomeStatus === "blocked"
|
|
194
|
+
).length;
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
title: "CEO Cockpit",
|
|
198
|
+
fleet,
|
|
199
|
+
attention,
|
|
200
|
+
reports,
|
|
201
|
+
governance: { blockedAttempts },
|
|
202
|
+
generatedFromReceipts: safeReceipts.length > 0,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export default deriveCeoCockpit;
|