@growthub/cli 0.14.2 → 0.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/SKILL.md +4 -2
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/agent-outcomes/route.js +85 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/apps/route.js +187 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +69 -1
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/patch/preflight/route.js +152 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +21 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +88 -1
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +72 -1
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/swarm-condition/route.js +2 -2
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-source/route.js +21 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflow/publish/route.js +338 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensPanel.jsx +1 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/CeoCockpit.jsx +532 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +36 -5
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +9 -1
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +11 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +22 -165
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-agent-teams.js +211 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-bootstrap-console.js +325 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-cockpit-console.js +206 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-publish.js +179 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +89 -5
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-app-registry.js +539 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-config.js +11 -2
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +23 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-outcome-receipts.js +157 -0
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-patch-policy.js +402 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +69 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +10 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/skills/governed-workspace-mutation/SKILL.md +203 -0
  31. package/package.json +2 -2
@@ -44,6 +44,11 @@ import {
44
44
  validateOrchestrationGraph
45
45
  } from "@/lib/orchestration-graph";
46
46
  import { resolveConnectorAction } from "@/lib/orchestration-sidecar-routing";
47
+ import {
48
+ nodeSandboxRecordRef,
49
+ patchSandboxRowInConfig,
50
+ withGraphSandboxRecordRefs
51
+ } from "@/lib/orchestration-publish";
47
52
  import { OrchestrationGraphCanvas } from "../data-model/components/OrchestrationGraphCanvas.jsx";
48
53
  import { OrchestrationGraphEmptyCanvas } from "../data-model/components/OrchestrationGraphEmptyCanvas.jsx";
49
54
  import { OrchestrationNodeConfigPanel } from "../data-model/components/OrchestrationNodeConfigPanel.jsx";
@@ -117,47 +122,6 @@ function resolveRegistryRefForSandbox(workspaceConfig, sandboxRow) {
117
122
  return firstRegistryRow ? { object: firstRegistryObject, row: firstRegistryRow } : null;
118
123
  }
119
124
 
120
- function patchSandboxRowInConfig(workspaceConfig, objectId, rowIndex, fields) {
121
- const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
122
- return {
123
- ...workspaceConfig,
124
- dataModel: {
125
- ...workspaceConfig.dataModel,
126
- objects: objects.map((object) => {
127
- if (object?.id !== objectId) return object;
128
- const rows = Array.isArray(object.rows) ? object.rows : [];
129
- return {
130
- ...object,
131
- rows: rows.map((row, index) => (index === rowIndex ? { ...row, ...fields } : row)),
132
- };
133
- }),
134
- },
135
- };
136
- }
137
-
138
- function nodeSandboxRecordRef(objectId, rowName, nodeId) {
139
- return {
140
- objectId: String(objectId || "").trim(),
141
- rowName: String(rowName || "").trim(),
142
- nodeId: String(nodeId || "").trim()
143
- };
144
- }
145
-
146
- function withGraphSandboxRecordRefs(graph, objectId, rowName) {
147
- const parsed = parseOrchestrationGraph(graph) || graph;
148
- if (!parsed || typeof parsed !== "object") return parsed;
149
- return {
150
- ...parsed,
151
- nodes: (Array.isArray(parsed.nodes) ? parsed.nodes : []).map((node) => ({
152
- ...node,
153
- config: {
154
- ...(node?.config || {}),
155
- sandboxRecordRef: nodeSandboxRecordRef(objectId, rowName, node?.id)
156
- }
157
- }))
158
- };
159
- }
160
-
161
125
  const WORKFLOW_ACTION_GROUPS = [
162
126
  {
163
127
  label: "Data",
@@ -201,90 +165,6 @@ function getWorkspaceObjectOptions(workspaceConfig) {
201
165
  }));
202
166
  }
203
167
 
204
- function normalizeDeltaTags(tags) {
205
- return Array.from(new Set((Array.isArray(tags) ? tags : [])
206
- .map((tag) => String(tag || "").trim().toLowerCase())
207
- .filter(Boolean)));
208
- }
209
-
210
- function inferDeltaTagsForWorkflowNode(node, config) {
211
- const tags = [];
212
- const type = String(node?.type || "").trim();
213
- const action = String(config?.action || node?.id || "").trim();
214
- if (type === "thinAdapter") tags.push("model", "prompt", "routing");
215
- if (type === "ai-agent") tags.push("model", "prompt", "output");
216
- if (type === "data-action" || type === "data-trigger") tags.push("input", "output");
217
- if (type === "flow-control") tags.push("routing");
218
- if (type === "core-action") tags.push("runtime");
219
- if (type === "human-input") tags.push("input");
220
- if (action.includes("search") || action.includes("filter")) tags.push("evaluation", "guardrail");
221
- if (action.includes("delete") || config?.confirmationRequired) tags.push("guardrail");
222
- if (action.includes("http") || config?.url || config?.method) tags.push("routing", "input", "output");
223
- if (action.includes("email")) tags.push("input", "output");
224
- if (action.includes("delay") || config?.duration || config?.unit) tags.push("runtime");
225
- if (config?.objectId || config?.fieldMap || config?.filters) tags.push("input", "output");
226
- if (config?.model || config?.prompt) tags.push("model", "prompt");
227
- return normalizeDeltaTags(tags);
228
- }
229
-
230
- function getNodeDeltaRecords(previousGraph, nextGraph) {
231
- const previousNodes = new Map(
232
- (Array.isArray(previousGraph?.nodes) ? previousGraph.nodes : [])
233
- .map((node) => [String(node?.id || ""), node])
234
- .filter(([id]) => id)
235
- );
236
-
237
- return (Array.isArray(nextGraph?.nodes) ? nextGraph.nodes : [])
238
- .map((node) => {
239
- const nodeId = String(node?.id || "").trim();
240
- if (!nodeId) return null;
241
- const previous = previousNodes.get(nodeId);
242
- const config = node?.config && typeof node.config === "object" && !Array.isArray(node.config) ? node.config : {};
243
- const previousConfig = previous?.config && typeof previous.config === "object" && !Array.isArray(previous.config)
244
- ? previous.config
245
- : {};
246
- const currentComparable = JSON.stringify({
247
- type: node?.type || "",
248
- sandbox: node?.sandbox || "",
249
- label: node?.label || "",
250
- subtitle: node?.subtitle || "",
251
- config
252
- });
253
- const previousComparable = JSON.stringify({
254
- type: previous?.type || "",
255
- sandbox: previous?.sandbox || "",
256
- label: previous?.label || "",
257
- subtitle: previous?.subtitle || "",
258
- config: previousConfig
259
- });
260
- const explicitTags = normalizeDeltaTags(config.deltaTags);
261
- const deltaTags = explicitTags.length > 0 ? explicitTags : inferDeltaTagsForWorkflowNode(node, config);
262
- const changeReason = String(config.changeReason || "").trim();
263
- const changed = currentComparable !== previousComparable;
264
- if (!changed && !changeReason && deltaTags.length === 0) return null;
265
- return {
266
- nodeId,
267
- nodeType: String(node?.type || ""),
268
- label: String(node?.label || node?.sandbox || nodeId),
269
- sandboxRecordRef: config.sandboxRecordRef || null,
270
- changeReason,
271
- deltaTags,
272
- requiresRetest: config.requiresRetest !== false,
273
- previous: previous ? {
274
- type: String(previous.type || ""),
275
- sandbox: String(previous.sandbox || ""),
276
- label: String(previous.label || "")
277
- } : null,
278
- next: {
279
- type: String(node.type || ""),
280
- sandbox: String(node.sandbox || ""),
281
- label: String(node.label || "")
282
- }
283
- };
284
- })
285
- .filter(Boolean);
286
- }
287
-
288
168
  function makeWorkflowNode(action, workspaceConfig, graph) {
289
169
  const baseId = String(action.id || action.type || "step").replace(/[^a-zA-Z0-9_-]+/g, "-");
290
170
  const existingIds = new Set((Array.isArray(graph?.nodes) ? graph.nodes : []).map((node) => String(node.id)));
@@ -606,54 +486,31 @@ export default function WorkflowSurface() {
606
486
 
607
487
  async function publishGraph() {
608
488
  if (resolved.rowIndex < 0 || !objectId) return;
489
+ // Publish is server-authoritative: POST /api/workspace/workflow/publish
490
+ // verifies the saved draft + passing test against the persisted row and
491
+ // owns the version bump, delta record, and draft → live promotion.
492
+ // Direct PATCH of live workflow fields is rejected by the runtime policy.
609
493
  const serialized = serializeCurrentGraph();
610
- const draftPassed = sandboxRow?.orchestrationDraftTestPassed === true || String(sandboxRow?.orchestrationDraftTestPassed || "") === "true";
611
- const testedConfig = String(sandboxRow?.orchestrationDraftTestedConfig || "");
612
- if (!draftPassed || testedConfig !== serialized) {
613
- setSaveMessage("Publish blocked. Save and test this exact draft successfully before publishing.");
494
+ const savedDraft = String(sandboxRow?.[draftFieldName] || "");
495
+ if (dirty || serialized !== savedDraft) {
496
+ setSaveMessage("Publish blocked. Save this draft first — publish promotes the saved, tested draft.");
614
497
  return;
615
498
  }
616
499
  setPublishing(true);
617
500
  setSaveMessage("");
618
501
  try {
619
- const currentVersion = Number(sandboxRow?.version || 1);
620
- const nextVersion = Number.isFinite(currentVersion) ? String(currentVersion + 1) : "1";
621
- const previousDeltas = Array.isArray(sandboxRow?.orchestrationDeltas) ? sandboxRow.orchestrationDeltas : [];
622
- const previousPublishedGraph = parseOrchestrationGraph(sandboxRow?.[effectiveFieldName]);
623
- const graphWithRefs = withGraphSandboxRecordRefs(orchestrationGraph, objectId, rowId);
624
- const nodeDeltas = getNodeDeltaRecords(previousPublishedGraph, graphWithRefs);
625
- const deltaTags = normalizeDeltaTags(nodeDeltas.flatMap((delta) => delta.deltaTags));
626
- const changeReason = nodeDeltas.map((delta) => delta.changeReason).filter(Boolean).join("\n");
627
- const next = patchSandboxRowInConfig(workspaceConfig, objectId, resolved.rowIndex, {
628
- [effectiveFieldName]: serialized,
629
- [draftFieldName]: "",
630
- version: nextVersion,
631
- lifecycleStatus: "live",
632
- orchestrationDraftStatus: "published",
633
- orchestrationDraftTestPassed: false,
634
- orchestrationDraftTestedConfig: "",
635
- orchestrationPublishedAt: new Date().toISOString(),
636
- orchestrationDeltas: [
637
- ...previousDeltas,
638
- {
639
- at: new Date().toISOString(),
640
- version: nextVersion,
641
- field: effectiveFieldName,
642
- action: "publish",
643
- previousVersion: String(sandboxRow?.version || "1"),
644
- draftTestedAt: sandboxRow?.orchestrationDraftLastTested || "",
645
- draftRunId: sandboxRow?.orchestrationDraftLastRunId || "",
646
- changeReason,
647
- deltaTags,
648
- nodeDeltas,
649
- nodeCount: Array.isArray(orchestrationGraph?.nodes) ? orchestrationGraph.nodes.length : 0,
650
- edgeCount: Array.isArray(orchestrationGraph?.edges) ? orchestrationGraph.edges.length : 0
651
- }
652
- ]
502
+ const res = await fetch("/api/workspace/workflow/publish", {
503
+ method: "POST",
504
+ headers: { "content-type": "application/json" },
505
+ body: JSON.stringify({ objectId, name: rowId, field: effectiveFieldName }),
653
506
  });
654
- await persistWorkspace(next);
507
+ const payload = await res.json();
508
+ if (!res.ok || !payload.ok) {
509
+ throw new Error(payload.error || "Publish failed");
510
+ }
511
+ setWorkspaceConfig(payload.workspaceConfig || workspaceConfig);
655
512
  setDirty(false);
656
- setSaveMessage(`Published orchestration config v${nextVersion}.`);
513
+ setSaveMessage(`Published orchestration config v${payload.version}.`);
657
514
  } catch (err) {
658
515
  setSaveMessage(err.message || "Publish failed");
659
516
  } finally {
@@ -0,0 +1,211 @@
1
+ /**
2
+ * CEO Agent Teams — the atomic, reusable team-configuration layer for the /ceo
3
+ * surface (CEO_PRIMITIVE_COCKPIT_ROADMAP_V1).
4
+ *
5
+ * Distinction this module encodes:
6
+ * - FLEET (runtime/oversight) = existing `swarm-workflows` rows, readiness,
7
+ * run state, failures, receipts, Background Tasks. UNCHANGED by this module.
8
+ * - AGENT TEAMS (atomic configuration) = reusable blueprints describing the
9
+ * orchestrator, sub-agent roles, skills, processes, workflow
10
+ * responsibilities, and outcome criteria of a swarm.
11
+ *
12
+ * This is a PURE module — no React, no fetch, no fs, no writes, no localStorage.
13
+ * It introduces NO new runtime, executor, API route, PATCH allowlist field, or
14
+ * object type: Agent Teams live in a governed Data Model object of the EXISTING
15
+ * `custom` objectType, created through the EXISTING `dataModel.object.create`
16
+ * helper/apply lane, and they only ever *inform* a `/swarm` proposal — the
17
+ * server still builds the `agent-swarm-v1` graph, the run still lands in
18
+ * `swarm-workflows`, and execution still happens through `sandbox-run`.
19
+ *
20
+ * An Agent Team record never executes anything.
21
+ */
22
+
23
+ export const AGENT_SWARM_TEAMS_OBJECT_ID = "agent-swarm-teams";
24
+ export const AGENT_SWARM_TEAMS_LABEL = "Agent Swarm Teams";
25
+
26
+ // Visible row columns. "Name" is the capital-N identity column (Data Model grid
27
+ // convention). Array-shaped fields are stored as readable strings so we never
28
+ // fight the row-value contract (no typed arrays in rows).
29
+ export const AGENT_SWARM_TEAMS_COLUMNS = [
30
+ "id",
31
+ "Name",
32
+ "status",
33
+ "teamPurpose",
34
+ "orchestratorRole",
35
+ "orchestratorPrompt",
36
+ "subAgentRoles",
37
+ "skills",
38
+ "processes",
39
+ "workflowResponsibilities",
40
+ "outcomeCriteria",
41
+ "defaultRunLocality",
42
+ "defaultAdapter",
43
+ "linkedSwarmWorkflowName",
44
+ "governanceNotes",
45
+ "createdAt",
46
+ "updatedAt",
47
+ ];
48
+
49
+ function clean(value) {
50
+ return typeof value === "string" ? value.trim() : "";
51
+ }
52
+
53
+ function slugify(value) {
54
+ return clean(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
55
+ }
56
+
57
+ // One clearly-labeled blueprint row so a freshly-created table shows the shape
58
+ // (and is honestly an example, not live state).
59
+ function blueprintRow() {
60
+ return {
61
+ id: "team-research-synthesis",
62
+ Name: "Example — Research & Synthesis Team",
63
+ status: "blueprint",
64
+ teamPurpose: "Reusable blueprint: research a topic and synthesize a cited brief.",
65
+ orchestratorRole: "Research Lead",
66
+ orchestratorPrompt: "Decompose the objective into independent research subtasks for the team.",
67
+ subAgentRoles: "Researcher; Analyst; Synthesizer",
68
+ skills: "web-research; summarization; critique",
69
+ processes: "gather → analyze → synthesize",
70
+ workflowResponsibilities: "Researcher gathers facts; Analyst stress-tests; Synthesizer writes the brief",
71
+ outcomeCriteria: "A cited brief with risks and a clear recommendation",
72
+ defaultRunLocality: "local",
73
+ defaultAdapter: "local-intelligence",
74
+ linkedSwarmWorkflowName: "",
75
+ governanceNotes: "Blueprint only — launch through /swarm; the run lands in swarm-workflows and emits receipts.",
76
+ createdAt: "",
77
+ updatedAt: "",
78
+ };
79
+ }
80
+
81
+ /** The governed Agent Teams object definition (existing `custom` objectType). */
82
+ export function buildAgentSwarmTeamsObject({ includeBlueprint = true } = {}) {
83
+ return {
84
+ id: AGENT_SWARM_TEAMS_OBJECT_ID,
85
+ label: AGENT_SWARM_TEAMS_LABEL,
86
+ objectType: "custom",
87
+ columns: AGENT_SWARM_TEAMS_COLUMNS.slice(),
88
+ rows: includeBlueprint ? [blueprintRow()] : [],
89
+ binding: { mode: "manual", source: AGENT_SWARM_TEAMS_LABEL },
90
+ };
91
+ }
92
+
93
+ /**
94
+ * A governed proposal that CREATES the Agent Teams table through the EXISTING
95
+ * `dataModel.object.create` helper/apply lane. No new proposal type, no new
96
+ * objectType — objectType stays `custom`.
97
+ */
98
+ export function buildCreateAgentTeamsProposal() {
99
+ return {
100
+ type: "dataModel.object.create",
101
+ affectedField: "dataModel",
102
+ payload: { object: buildAgentSwarmTeamsObject({ includeBlueprint: true }) },
103
+ rationale: "Create the governed Agent Swarm Teams table — reusable swarm blueprints (config, not runtime).",
104
+ };
105
+ }
106
+
107
+ export function findAgentTeamsObject(workspaceConfig) {
108
+ const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
109
+ return objects.find((o) => o?.id === AGENT_SWARM_TEAMS_OBJECT_ID) || null;
110
+ }
111
+
112
+ export function findAgentTeams(workspaceConfig) {
113
+ const object = findAgentTeamsObject(workspaceConfig);
114
+ const rows = Array.isArray(object?.rows) ? object.rows : [];
115
+ const seen = new Set();
116
+ return rows
117
+ .filter((row) => row && clean(row.Name))
118
+ .map((row, index) => {
119
+ const name = clean(row.Name);
120
+ // Stable atomic identity: explicit id, else a slug of Name, else index —
121
+ // de-duplicated so two same-named blueprints never collide as React keys
122
+ // (the same fix applied to fleet reports).
123
+ let teamId = clean(row.id) || slugify(name) || `team-${index + 1}`;
124
+ while (seen.has(teamId)) teamId = `${teamId}-${index + 1}`;
125
+ seen.add(teamId);
126
+ return {
127
+ teamId,
128
+ name,
129
+ status: clean(row.status) || "blueprint",
130
+ teamPurpose: clean(row.teamPurpose),
131
+ orchestratorRole: clean(row.orchestratorRole),
132
+ orchestratorPrompt: clean(row.orchestratorPrompt),
133
+ subAgentRoles: clean(row.subAgentRoles),
134
+ skills: clean(row.skills),
135
+ processes: clean(row.processes),
136
+ workflowResponsibilities: clean(row.workflowResponsibilities),
137
+ outcomeCriteria: clean(row.outcomeCriteria),
138
+ defaultRunLocality: clean(row.defaultRunLocality),
139
+ defaultAdapter: clean(row.defaultAdapter),
140
+ linkedSwarmWorkflowName: clean(row.linkedSwarmWorkflowName),
141
+ governanceNotes: clean(row.governanceNotes),
142
+ };
143
+ });
144
+ }
145
+
146
+ /**
147
+ * Project the Agent Teams configuration layer for the cockpit. Pure.
148
+ * { present, count, teams[], canCreate }
149
+ */
150
+ export function deriveAgentTeamsState({ workspaceConfig } = {}) {
151
+ const object = findAgentTeamsObject(workspaceConfig);
152
+ const teams = findAgentTeams(workspaceConfig);
153
+ return {
154
+ objectId: AGENT_SWARM_TEAMS_OBJECT_ID,
155
+ present: Boolean(object),
156
+ count: teams.length,
157
+ teams,
158
+ // The table is created through the governed create lane when absent.
159
+ canCreate: !object,
160
+ };
161
+ }
162
+
163
+ function joinAgents(subAgentRoles) {
164
+ return clean(subAgentRoles)
165
+ .split(/[;,\n]/)
166
+ .map((part) => part.trim())
167
+ .filter(Boolean);
168
+ }
169
+
170
+ /**
171
+ * Build the /swarm composer seed text from an Agent Team blueprint. This is
172
+ * PROPOSE-ONLY seed copy: it prefills the helper composer so the user proposes
173
+ * a governed swarm "from this blueprint". The model + server still produce the
174
+ * proposal and build the agent-swarm-v1 graph; nothing here executes.
175
+ */
176
+ export function buildSwarmIntentFromTeam(team) {
177
+ if (!team) return "Propose a governed agent swarm:";
178
+ const agents = joinAgents(team.subAgentRoles);
179
+ const target = [
180
+ team.defaultAdapter ? `adapter ${team.defaultAdapter}` : "",
181
+ team.defaultRunLocality ? `${team.defaultRunLocality} run target` : "",
182
+ ].filter(Boolean).join(", ");
183
+ // Carry the full atomic configuration forward so the blueprint is a true
184
+ // configuration inversion of the swarm — still propose-only seed copy; the
185
+ // server builds the agent-swarm-v1 graph and nothing executes here.
186
+ const parts = [
187
+ `Propose a governed agent swarm from the Agent Team blueprint "${team.name}".`,
188
+ team.teamPurpose ? `Objective: ${team.teamPurpose}` : "",
189
+ team.orchestratorRole || team.orchestratorPrompt
190
+ ? `Orchestrator${team.orchestratorRole ? ` (${team.orchestratorRole})` : ""}: ${team.orchestratorPrompt || "plan the work for the team"}.`
191
+ : "",
192
+ agents.length ? `Sub-agents: ${agents.join(", ")}.` : "",
193
+ team.skills ? `Skills: ${team.skills}.` : "",
194
+ team.processes ? `Process: ${team.processes}.` : "",
195
+ team.workflowResponsibilities ? `Workflow responsibilities: ${team.workflowResponsibilities}.` : "",
196
+ team.outcomeCriteria ? `Outcome criteria: ${team.outcomeCriteria}.` : "",
197
+ target ? `Preferred execution target: ${target}.` : "",
198
+ team.governanceNotes ? `Governance notes: ${team.governanceNotes}.` : "",
199
+ ];
200
+ return parts.filter(Boolean).join(" ");
201
+ }
202
+
203
+ export function summarizeTeam(team) {
204
+ const agents = joinAgents(team?.subAgentRoles).length;
205
+ return [
206
+ team?.orchestratorRole ? `orchestrator: ${team.orchestratorRole}` : null,
207
+ agents ? `${agents} sub-agent${agents === 1 ? "" : "s"}` : null,
208
+ ].filter(Boolean).join(" · ");
209
+ }
210
+
211
+ export default deriveAgentTeamsState;