@growthub/cli 0.14.3 → 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.
@@ -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;
@@ -241,7 +241,9 @@ function checkSandboxRow(row, currentRow, path, violations) {
241
241
 
242
242
  const incomingStatus = String(row.lifecycleStatus ?? "").trim().toLowerCase();
243
243
  const currentStatus = String(currentRow?.lifecycleStatus ?? "").trim().toLowerCase();
244
+ const isHelperSetupRow = rowName(row) === "workspace-helper";
244
245
  if (incomingStatus === "live" && currentStatus !== "live") {
246
+ if (isHelperSetupRow) return;
245
247
  violations.push(violation(
246
248
  "live_publish_via_patch",
247
249
  `${path}.lifecycleStatus`,
@@ -298,6 +298,75 @@ const DASHBOARD_TEMPLATES = [
298
298
  dashboard: { name: "Blank", status: "draft" },
299
299
  widgets: []
300
300
  },
301
+ {
302
+ id: "ceo-daily-operating",
303
+ name: "CEO Daily Operating Dashboard",
304
+ description: "An executive operating surface for the CEO loop: today's focus, the loop scorecard, direct reports, blocked work, reusable Agent Team blueprints, and governance receipts. Companion to the CEO Cockpit (/ceo) — Agent Teams are the atomic config layer, the Fleet is the runtime, and the live next move lives in the cockpit.",
305
+ category: "reporting",
306
+ bestFor: ["Founders", "Operators", "Workspace orchestrators"],
307
+ tags: ["ceo", "operating", "executive", "daily", "agent-teams"],
308
+ preview: {
309
+ layout: "operating-grid",
310
+ summary: "Today's focus, loop scorecard, direct reports, blocked work, Agent Team blueprints, governance receipts, and ritual notes"
311
+ },
312
+ dashboard: { name: "CEO Daily Operating Dashboard", status: "draft" },
313
+ widgets: [
314
+ createWidget("rich-text", "Today's CEO Focus", { x: 0, y: 0, w: 8, h: 4 }, {
315
+ text: "Set this each morning: the one highest-leverage move today, what is blocked, what needs review, and what to launch next. The live, computed next move is always in the CEO Cockpit — open it with /ceo.",
316
+ binding: { mode: "manual", source: "Manual text", rows: [] }
317
+ }),
318
+ createWidget("chart", "CEO Loop Scorecard", { x: 8, y: 0, w: 4, h: 4 }, {
319
+ values: [5, 4, 3, 3, 1, 3],
320
+ binding: { mode: "manual", source: "Sample — Created · Ready · Launched · Completed · Blocked · Reviewed", rows: [] }
321
+ }),
322
+ createWidget("view", "Direct Reports / Swarm Fleet", { x: 0, y: 4, w: 7, h: 5 }, {
323
+ source: "Sample",
324
+ layout: "Table",
325
+ columns: ["Workflow", "State", "Readiness", "Last Outcome", "Next Move"],
326
+ rows: [
327
+ { Workflow: "Example research swarm", State: "Completed", Readiness: "Ready", "Last Outcome": "Success", "Next Move": "Review output" },
328
+ { Workflow: "Example outreach swarm", State: "Not run yet", Readiness: "Ready", "Last Outcome": "—", "Next Move": "Launch" },
329
+ { Workflow: "Example audit swarm", State: "Blocked", Readiness: "Needs target", "Last Outcome": "—", "Next Move": "Set execution target" }
330
+ ],
331
+ binding: { mode: "manual", source: "Sample rows — the live fleet is in the CEO Cockpit (/ceo)", rows: [] }
332
+ }),
333
+ createWidget("view", "Blocked or Failing Work", { x: 7, y: 4, w: 5, h: 5 }, {
334
+ source: "Sample",
335
+ layout: "Table",
336
+ columns: ["Workflow", "Blocker", "Severity", "Next Action"],
337
+ rows: [
338
+ { Workflow: "Example audit swarm", Blocker: "No execution target", Severity: "High", "Next Action": "Set local adapter" },
339
+ { Workflow: "Example billing swarm", Blocker: "Last run failed", Severity: "High", "Next Action": "Review + re-run" }
340
+ ],
341
+ binding: { mode: "manual", source: "Sample rows — live blockers are in the CEO Cockpit (/ceo)", rows: [] }
342
+ }),
343
+ createWidget("view", "Agent Team Blueprints", { x: 0, y: 9, w: 6, h: 4 }, {
344
+ source: "Sample",
345
+ layout: "Table",
346
+ columns: ["Team", "Orchestrator", "Sub-agents", "Outcome"],
347
+ rows: [
348
+ { Team: "Research & Synthesis", Orchestrator: "Research Lead", "Sub-agents": "Researcher; Analyst; Synthesizer", Outcome: "Cited brief + recommendation" },
349
+ { Team: "Outreach", Orchestrator: "Campaign Lead", "Sub-agents": "Writer; Reviewer", Outcome: "Approved sequence" }
350
+ ],
351
+ binding: { mode: "manual", source: "Sample blueprints — manage real teams in the Agent Swarm Teams object and launch via /ceo (config layer, not runtime)", rows: [] }
352
+ }),
353
+ createWidget("view", "Governance Receipts / Decisions", { x: 6, y: 9, w: 6, h: 4 }, {
354
+ source: "Sample",
355
+ layout: "Table",
356
+ columns: ["Time", "Lane", "Action", "Outcome", "Receipt"],
357
+ rows: [
358
+ { Time: "09:14", Lane: "execution-proof", Action: "sandbox-run", Outcome: "completed", Receipt: "rcpt_sample_1" },
359
+ { Time: "09:31", Lane: "governed-proposal", Action: "helper/apply", Outcome: "applied", Receipt: "rcpt_sample_2" },
360
+ { Time: "09:42", Lane: "untrusted-direct", Action: "PATCH", Outcome: "blocked", Receipt: "rcpt_sample_3" }
361
+ ],
362
+ binding: { mode: "manual", source: "Sample rows — live stream: GET /api/workspace/agent-outcomes", rows: [] }
363
+ }),
364
+ createWidget("rich-text", "Daily Ritual Notes", { x: 0, y: 13, w: 12, h: 3 }, {
365
+ text: "Morning: set today's focus. Midday: launch or review. Afternoon: unblock the failing one first. End of day: confirm the receipts. Define reusable Agent Teams, then launch them as governed swarms — runs land in the Fleet. Open /ceo for the live next move.",
366
+ binding: { mode: "manual", source: "Manual text", rows: [] }
367
+ })
368
+ ]
369
+ },
301
370
  {
302
371
  id: "client-portal",
303
372
  name: "Client Portal",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growthub/cli",
3
- "version": "0.14.3",
3
+ "version": "0.14.4",
4
4
  "description": "CLI control plane for Growthub Local and Agent Workspace as Code: export, fork, inspect, operate, sync, and optionally activate governed AI workspaces.",
5
5
  "type": "module",
6
6
  "bin": {