@growthub/cli 0.13.2 → 0.13.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +184 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +24 -2
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +14 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/login/route.js +74 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/logout/route.js +67 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/status/route.js +77 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +72 -4
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +326 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +123 -27
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +6 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +224 -1
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +754 -92
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxAgentAuthPanel.jsx +224 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxRunPanel.jsx +32 -1
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/WorkspaceGraphInspectorPanel.jsx +226 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +530 -9
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +8 -1
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +10 -7
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/RunSetupPanel.jsx +261 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +119 -9
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +779 -138
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +91 -14
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/sandbox-environment-primitive.md +35 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +923 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +28 -3
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +216 -5
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +412 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +366 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +34 -3
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-eligibility.js +50 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-redaction.js +64 -0
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +665 -0
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-host-catalog.js +168 -0
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +595 -0
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +164 -7
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +11 -0
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-graph.js +646 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-selectors.js +249 -0
  39. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +1186 -0
  40. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +111 -1
  41. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +14 -0
  42. package/package.json +1 -1
@@ -8,11 +8,14 @@ import {
8
8
  extractApiRegistryCallNode,
9
9
  extractInputNode,
10
10
  extractTransformConfig,
11
+ isAgentSwarmGraph,
11
12
  normalizeJsonAtPath,
12
13
  parseOrchestrationGraph,
13
14
  redactSecretsFromText,
14
15
  substituteVariables
15
16
  } from "./orchestration-graph.js";
17
+ import { buildInputPayloadForRunner } from "./orchestration-run-inputs.js";
18
+ import { runAgentSwarmGraphIfPresent } from "./orchestration-agent-swarm.js";
16
19
 
17
20
  function normalizeMethod(value) {
18
21
  const method = String(value || "GET").trim().toUpperCase();
@@ -255,11 +258,27 @@ async function executeApiRegistryCall(workspaceConfig, nodeConfig, inputPayload,
255
258
  /**
256
259
  * Run a growthub-native orchestration graph when present on the sandbox row.
257
260
  * Returns null when the row has no executable graph (caller falls back to adapter path).
261
+ *
262
+ * `runInputs` (V2) — normalized manual run input envelope. When provided, the
263
+ * non-secret values override matching keys in the input node's samplePayload
264
+ * for `human-input` / form workflows. Secret values (those stored as
265
+ * `{ secretRef }`) are never expanded into the runner payload.
258
266
  */
259
- async function runOrchestrationGraphIfPresent({ workspaceConfig, row, timeoutMs }) {
267
+ async function runOrchestrationGraphIfPresent({ workspaceConfig, row, timeoutMs, runInputs, executionContext }) {
260
268
  const graph = parseOrchestrationGraph(row?.orchestrationGraph || row?.orchestrationConfig);
261
269
  if (!graph || String(graph.provider || "").trim() !== "growthub-native") return null;
262
270
 
271
+ if (isAgentSwarmGraph(graph)) {
272
+ return await runAgentSwarmGraphIfPresent({
273
+ workspaceConfig,
274
+ row,
275
+ graph,
276
+ timeoutMs,
277
+ runInputs,
278
+ executionContext
279
+ });
280
+ }
281
+
263
282
  const apiNode = extractApiRegistryCallNode(graph);
264
283
  if (!apiNode?.config) {
265
284
  return {
@@ -274,7 +293,10 @@ async function runOrchestrationGraphIfPresent({ workspaceConfig, row, timeoutMs
274
293
  }
275
294
 
276
295
  const inputNode = extractInputNode(graph);
277
- const inputPayload = parseInputPayload(inputNode);
296
+ const baseInputPayload = parseInputPayload(inputNode);
297
+ const manualPayload = runInputs ? buildInputPayloadForRunner(runInputs) : {};
298
+ const inputPayload = { ...baseInputPayload, ...manualPayload };
299
+ const consumedInputKeys = Object.keys(manualPayload);
278
300
  const transformConfig = extractTransformConfig(graph);
279
301
  const resultNode = graph.nodes?.find((n) => n?.type === "tool-result");
280
302
  const successCodes = Array.isArray(resultNode?.config?.successStatusCodes)
@@ -314,7 +336,10 @@ async function runOrchestrationGraphIfPresent({ workspaceConfig, row, timeoutMs
314
336
  adapterMeta: {
315
337
  ...(raw.adapterMeta || {}),
316
338
  orchestrationProvider: graph.provider,
317
- orchestrationVersion: graph.version
339
+ orchestrationVersion: graph.version,
340
+ ...(consumedInputKeys.length
341
+ ? { runInputsConsumed: consumedInputKeys, runInputSource: String(runInputs?.source || "manual") }
342
+ : {})
318
343
  }
319
344
  };
320
345
  }
@@ -151,11 +151,16 @@ function validateOrchestrationGraph(graph) {
151
151
  errors.push(`${prefix}.type "${type}" is not a known node type`);
152
152
  }
153
153
  });
154
- const hasThinAdapter = graph.nodes.some((n) => n?.type === "thinAdapter");
155
- const hasApi = graph.nodes.some((n) => n?.type === "api-registry-call");
156
- const hasResult = graph.nodes.some((n) => n?.type === "tool-result");
157
- if (!hasThinAdapter && !hasApi) errors.push("orchestrationGraph requires an api-registry-call node");
158
- if (!hasThinAdapter && !hasResult) errors.push("orchestrationGraph requires a tool-result node");
154
+ if (isAgentSwarmGraph(graph)) {
155
+ const swarmCheck = validateAgentSwarmGraph(graph);
156
+ if (!swarmCheck.ok) errors.push(...swarmCheck.errors);
157
+ } else {
158
+ const hasThinAdapter = graph.nodes.some((n) => n?.type === "thinAdapter");
159
+ const hasApi = graph.nodes.some((n) => n?.type === "api-registry-call");
160
+ const hasResult = graph.nodes.some((n) => n?.type === "tool-result");
161
+ if (!hasThinAdapter && !hasApi) errors.push("orchestrationGraph requires an api-registry-call node");
162
+ if (!hasThinAdapter && !hasResult) errors.push("orchestrationGraph requires a tool-result node");
163
+ }
159
164
  }
160
165
  if (!Array.isArray(graph.edges)) {
161
166
  errors.push("orchestrationGraph.edges must be an array");
@@ -680,6 +685,207 @@ function addCanonicalNodeToGraph(graph, nodeId, registryRow, options = {}) {
680
685
  return { ...parsed, nodes, edges };
681
686
  }
682
687
 
688
+ const AGENT_SWARM_EXECUTION_MODE = "agent-swarm-v1";
689
+
690
+ /**
691
+ * Detect whether a graph is an agent-swarm-v1 control plane. Swarm graphs are
692
+ * encoded as growthub-native graphs whose root carries
693
+ * `executionMode: "agent-swarm-v1"` and that contain at least one orchestrator
694
+ * (`thinAdapter`) or subagent (`ai-agent`) node.
695
+ */
696
+ function isAgentSwarmGraph(graph) {
697
+ const parsed = parseOrchestrationGraph(graph) || graph;
698
+ if (!parsed || typeof parsed !== "object") return false;
699
+ if (String(parsed.provider || "").trim() !== "growthub-native") return false;
700
+ if (String(parsed.executionMode || "").trim() !== AGENT_SWARM_EXECUTION_MODE) return false;
701
+ const nodes = Array.isArray(parsed.nodes) ? parsed.nodes : [];
702
+ if (!nodes.length) return false;
703
+ return nodes.some((n) => n?.type === "thinAdapter" || n?.type === "ai-agent");
704
+ }
705
+
706
+ /**
707
+ * Split a swarm graph into its semantic parts so the runtime and UI can reason
708
+ * about it without re-walking the node list. Returns `null` when the graph is
709
+ * not a recognized swarm.
710
+ */
711
+ function extractSwarmNodes(graph) {
712
+ const parsed = parseOrchestrationGraph(graph) || graph;
713
+ if (!isAgentSwarmGraph(parsed)) return null;
714
+ const nodes = Array.isArray(parsed.nodes) ? parsed.nodes : [];
715
+ const orchestrator = nodes.find((n) => n?.type === "thinAdapter") || null;
716
+ const subagents = nodes.filter((n) => n?.type === "ai-agent");
717
+ const synthesis = nodes.find((n) => n?.type === "tool-result") || null;
718
+ const humanInputs = nodes.filter((n) => n?.type === "human-input");
719
+ const flowControls = nodes.filter((n) => n?.type === "flow-control");
720
+ const swarmConfig = (parsed.swarm && typeof parsed.swarm === "object" && !Array.isArray(parsed.swarm))
721
+ ? parsed.swarm
722
+ : {};
723
+ return {
724
+ graph: parsed,
725
+ orchestrator,
726
+ subagents,
727
+ synthesis,
728
+ humanInputs,
729
+ flowControls,
730
+ swarmConfig
731
+ };
732
+ }
733
+
734
+ /**
735
+ * Build the default scaffold for a new agent-swarm-v1 graph. The shape mirrors
736
+ * the Kimi screenshots — an orchestrator, two specialized subagents, a
737
+ * synthesis tool-result — but reuses existing node types so no schema change
738
+ * is required.
739
+ */
740
+ function buildDefaultAgentSwarmGraph(options = {}) {
741
+ const agentHost = String(options.agentHost || "").trim();
742
+ const subagents = Array.isArray(options.subagents) && options.subagents.length > 0
743
+ ? options.subagents
744
+ : [
745
+ {
746
+ id: "subagent-researcher",
747
+ role: "Researcher",
748
+ description: "Gathers facts from the run input and the orchestrator's plan.",
749
+ taskPrompt: "Investigate the orchestrator's plan and gather the relevant facts.",
750
+ tools: ["read", "summarize"],
751
+ required: true
752
+ },
753
+ {
754
+ id: "subagent-analyst",
755
+ role: "Analyst",
756
+ description: "Stress-tests assumptions and surfaces risks.",
757
+ taskPrompt: "Identify risks, assumptions, and dependencies in the orchestrator's plan.",
758
+ tools: ["read", "critique"],
759
+ required: true
760
+ }
761
+ ];
762
+
763
+ const nodes = [
764
+ {
765
+ id: "orchestrator",
766
+ type: "thinAdapter",
767
+ label: "Orchestrator",
768
+ subtitle: "Plans subagent dispatch",
769
+ sandbox: "orchestrator",
770
+ config: {
771
+ executionPolicy: "parallel",
772
+ prompt: String(options.orchestratorPrompt || "Decompose the task into independent subtasks for the listed subagents.").trim(),
773
+ inputBinding: "{{input.payload}}",
774
+ outputKey: "plan"
775
+ }
776
+ },
777
+ ...subagents.map((agent) => ({
778
+ id: String(agent.id || agent.role || "subagent").replace(/[^a-zA-Z0-9_-]+/g, "-"),
779
+ type: "ai-agent",
780
+ label: String(agent.role || agent.id || "Subagent"),
781
+ subtitle: "Swarm subagent",
782
+ config: {
783
+ role: String(agent.role || agent.id || "Subagent"),
784
+ description: String(agent.description || "").trim(),
785
+ taskPrompt: String(agent.taskPrompt || "").trim(),
786
+ tools: Array.isArray(agent.tools) ? agent.tools.map((t) => String(t || "").trim()).filter(Boolean) : [],
787
+ agentHost: String(agent.agentHost || agentHost || "").trim(),
788
+ adapter: String(agent.adapter || "").trim(),
789
+ required: agent.required !== false,
790
+ canReadWorkspace: true,
791
+ canWriteDraft: false,
792
+ networkAccess: agent.networkAccess === true,
793
+ maxTokens: Number.isFinite(Number(agent.maxTokens)) && Number(agent.maxTokens) > 0
794
+ ? Math.floor(Number(agent.maxTokens))
795
+ : 0,
796
+ timeoutMs: Number.isFinite(Number(agent.timeoutMs)) && Number(agent.timeoutMs) > 0
797
+ ? Math.floor(Number(agent.timeoutMs))
798
+ : 0
799
+ }
800
+ })),
801
+ {
802
+ id: "synthesis",
803
+ type: "tool-result",
804
+ label: "Final synthesis",
805
+ subtitle: "Aggregate subagent results",
806
+ config: {
807
+ successStatusCodes: [200],
808
+ writeLastResponse: true,
809
+ writeSourceRecord: true,
810
+ outputMode: "swarm-summary",
811
+ statusField: "status",
812
+ lastTestedField: "lastTested",
813
+ outcomePrompt: String(options.outcomePrompt || "Confirm every required subagent completed and write the final answer.").trim()
814
+ }
815
+ }
816
+ ];
817
+
818
+ const edges = [
819
+ ...subagents.map((agent) => ({
820
+ from: "orchestrator",
821
+ to: String(agent.id || agent.role || "subagent").replace(/[^a-zA-Z0-9_-]+/g, "-"),
822
+ passes: "subtask-assignment"
823
+ })),
824
+ ...subagents.map((agent) => ({
825
+ from: String(agent.id || agent.role || "subagent").replace(/[^a-zA-Z0-9_-]+/g, "-"),
826
+ to: "synthesis",
827
+ passes: "subtask-result"
828
+ }))
829
+ ];
830
+
831
+ const maxConcurrency = Math.max(1, Number(options.maxConcurrency) || subagents.length);
832
+ const rewardWeights = options.rewardWeights && typeof options.rewardWeights === "object"
833
+ ? options.rewardWeights
834
+ : { parallel: 0.25, finish: 0.35, outcome: 0.4 };
835
+
836
+ return {
837
+ version: 1,
838
+ provider: "growthub-native",
839
+ executionMode: AGENT_SWARM_EXECUTION_MODE,
840
+ swarm: {
841
+ maxConcurrency,
842
+ rewardWeights: {
843
+ parallel: Number(rewardWeights.parallel) || 0,
844
+ finish: Number(rewardWeights.finish) || 0,
845
+ outcome: Number(rewardWeights.outcome) || 0
846
+ },
847
+ outcomeCriteria: String(options.outcomeCriteria || "All required subagents complete and synthesis runs without error.").trim()
848
+ },
849
+ nodes,
850
+ edges
851
+ };
852
+ }
853
+
854
+ /**
855
+ * Schema-aware validation for agent-swarm-v1 graphs. Returns `{ ok, errors }`
856
+ * with concrete user-facing messages. Used both by validateOrchestrationGraph
857
+ * and the WorkflowSurface Test gate so the user never gets a runtime "swarm
858
+ * subagent has no prompt-capable adapter" error at Test time when the static
859
+ * config already revealed the issue.
860
+ */
861
+ function validateAgentSwarmGraph(graph) {
862
+ const errors = [];
863
+ if (!graph || typeof graph !== "object") {
864
+ return { ok: false, errors: ["agent-swarm graph must be an object"] };
865
+ }
866
+ const extracted = extractSwarmNodes(graph);
867
+ if (!extracted) return { ok: false, errors: ["graph is not an agent-swarm-v1 graph"] };
868
+ const { orchestrator, subagents, synthesis } = extracted;
869
+ if (!orchestrator) errors.push("missing orchestrator (thinAdapter) node");
870
+ if (subagents.length === 0) errors.push("agent-swarm requires at least one ai-agent subagent");
871
+ subagents.forEach((node, index) => {
872
+ const cfg = node?.config || {};
873
+ const role = String(cfg.role || node?.label || "").trim();
874
+ if (!role) errors.push(`subagent[${index}] (${node?.id || "?"}) must declare a role`);
875
+ if (!String(cfg.taskPrompt || cfg.prompt || "").trim()) {
876
+ errors.push(`subagent "${role || node?.id}" must declare a task prompt`);
877
+ }
878
+ const adapter = String(cfg.adapter || "").trim();
879
+ if (adapter && !["local-agent-host", "local-intelligence"].includes(adapter)) {
880
+ errors.push(`subagent "${role || node?.id}" sets adapter="${adapter}" which cannot execute prompts; use local-agent-host or local-intelligence`);
881
+ }
882
+ });
883
+ if (!synthesis) {
884
+ errors.push("agent-swarm graph should include a tool-result synthesis node to evaluate outcome");
885
+ }
886
+ return { ok: errors.length === 0, errors };
887
+ }
888
+
683
889
  function redactSecretsFromText(text) {
684
890
  let out = String(text || "");
685
891
  for (const pattern of [
@@ -699,9 +905,14 @@ export {
699
905
  FILTER_OPERATORS,
700
906
  FILTER_CONJUNCTIONS,
701
907
  CANONICAL_NODE_ORDER,
908
+ AGENT_SWARM_EXECUTION_MODE,
702
909
  buildBlankOrchestrationGraphShell,
703
910
  buildDefaultOrchestrationGraphFromRegistry,
911
+ buildDefaultAgentSwarmGraph,
704
912
  buildCanonicalNode,
913
+ isAgentSwarmGraph,
914
+ extractSwarmNodes,
915
+ validateAgentSwarmGraph,
705
916
  isOrchestrationGraphEmpty,
706
917
  getOrchestrationGraphUiState,
707
918
  getNextCanonicalNodeId,