@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
|
@@ -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
|
|
611
|
-
|
|
612
|
-
|
|
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
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
|
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${
|
|
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;
|