@growthub/cli 0.13.4 → 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.
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +184 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +25 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +326 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +6 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +88 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +41 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/WorkspaceGraphInspectorPanel.jsx +226 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +16 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +49 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +14 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +923 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +14 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +216 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +28 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +43 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +3 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +36 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +53 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-graph.js +646 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-selectors.js +249 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +1186 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +5 -0
- package/package.json +1 -1
|
@@ -8,12 +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";
|
|
16
17
|
import { buildInputPayloadForRunner } from "./orchestration-run-inputs.js";
|
|
18
|
+
import { runAgentSwarmGraphIfPresent } from "./orchestration-agent-swarm.js";
|
|
17
19
|
|
|
18
20
|
function normalizeMethod(value) {
|
|
19
21
|
const method = String(value || "GET").trim().toUpperCase();
|
|
@@ -262,10 +264,21 @@ async function executeApiRegistryCall(workspaceConfig, nodeConfig, inputPayload,
|
|
|
262
264
|
* for `human-input` / form workflows. Secret values (those stored as
|
|
263
265
|
* `{ secretRef }`) are never expanded into the runner payload.
|
|
264
266
|
*/
|
|
265
|
-
async function runOrchestrationGraphIfPresent({ workspaceConfig, row, timeoutMs, runInputs }) {
|
|
267
|
+
async function runOrchestrationGraphIfPresent({ workspaceConfig, row, timeoutMs, runInputs, executionContext }) {
|
|
266
268
|
const graph = parseOrchestrationGraph(row?.orchestrationGraph || row?.orchestrationConfig);
|
|
267
269
|
if (!graph || String(graph.provider || "").trim() !== "growthub-native") return null;
|
|
268
270
|
|
|
271
|
+
if (isAgentSwarmGraph(graph)) {
|
|
272
|
+
return await runAgentSwarmGraphIfPresent({
|
|
273
|
+
workspaceConfig,
|
|
274
|
+
row,
|
|
275
|
+
graph,
|
|
276
|
+
timeoutMs,
|
|
277
|
+
runInputs,
|
|
278
|
+
executionContext
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
269
282
|
const apiNode = extractApiRegistryCallNode(graph);
|
|
270
283
|
if (!apiNode?.config) {
|
|
271
284
|
return {
|
|
@@ -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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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,
|
|
@@ -160,6 +160,9 @@ function buildLogChildren(record, summary) {
|
|
|
160
160
|
|
|
161
161
|
function buildRunLogTree(record) {
|
|
162
162
|
if (!record || typeof record !== "object") return [];
|
|
163
|
+
if (Array.isArray(record.logTree) && record.logTree.length > 0) {
|
|
164
|
+
return record.logTree;
|
|
165
|
+
}
|
|
163
166
|
const summary = deriveRunSummary(record);
|
|
164
167
|
const durationMs = clampNumber(record?.durationMs) || 0;
|
|
165
168
|
const attemptChildren = buildLogChildren(record, summary);
|
|
@@ -224,6 +227,29 @@ function normalizeRunConsoleRecord(record) {
|
|
|
224
227
|
const safeInput = rawInput ? redactRunInputsEnvelope(rawInput) : null;
|
|
225
228
|
const inputSummary = safeInput ? summarizeRunInputs(safeInput) : null;
|
|
226
229
|
const exports = buildExportsForRecord(record, stdoutText, stderrText, outputText);
|
|
230
|
+
// Workspace Metadata Graph V1 — safe lineage projection. Names only,
|
|
231
|
+
// no secrets. Lets the Live Runs Console UI render "this run came from
|
|
232
|
+
// sandbox X / workflow Y / adapter Z / agent host A" without re-deriving
|
|
233
|
+
// the relationships from raw fields.
|
|
234
|
+
const lineage = {
|
|
235
|
+
runId: safeString(record.runId).trim(),
|
|
236
|
+
objectId: safeString(record.objectId).trim(),
|
|
237
|
+
sandboxName: safeString(record.name || record.sandboxName).trim(),
|
|
238
|
+
workflowRowId: safeString(record.name || record.sandboxName).trim(),
|
|
239
|
+
workflowMetadataId: safeString(record.objectId).trim() && safeString(record.name || record.sandboxName).trim()
|
|
240
|
+
? `workflow:${safeString(record.objectId).trim()}:${safeString(record.name || record.sandboxName).trim()}`
|
|
241
|
+
: "",
|
|
242
|
+
sandboxMetadataId: safeString(record.objectId).trim() && safeString(record.name || record.sandboxName).trim()
|
|
243
|
+
? `sandbox:${safeString(record.objectId).trim()}:${safeString(record.name || record.sandboxName).trim()}`
|
|
244
|
+
: "",
|
|
245
|
+
adapter: safeString(record.adapter).trim(),
|
|
246
|
+
agentHost: safeString(record.agentHost).trim(),
|
|
247
|
+
runtime: safeString(record.runtime).trim(),
|
|
248
|
+
runLocality: safeString(record.runLocality).trim(),
|
|
249
|
+
inputFieldCount: inputSummary ? inputSummary.fieldCount : 0,
|
|
250
|
+
inputSource: inputSummary ? inputSummary.source : "",
|
|
251
|
+
hasOutput: Boolean(outputText)
|
|
252
|
+
};
|
|
227
253
|
|
|
228
254
|
return {
|
|
229
255
|
runId: safeString(record.runId).trim(),
|
|
@@ -275,6 +301,8 @@ function normalizeRunConsoleRecord(record) {
|
|
|
275
301
|
adapterMeta,
|
|
276
302
|
templateTrace
|
|
277
303
|
},
|
|
304
|
+
lineage,
|
|
305
|
+
swarm: record.swarm && typeof record.swarm === "object" ? record.swarm : null,
|
|
278
306
|
logTree: buildRunLogTree(record)
|
|
279
307
|
};
|
|
280
308
|
}
|
|
@@ -307,6 +307,48 @@ function buildInputPayloadForRunner(envelope) {
|
|
|
307
307
|
return out;
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Workspace Metadata Graph V1 — typed run-input descriptors.
|
|
312
|
+
*
|
|
313
|
+
* Returns a metadata-compatible list:
|
|
314
|
+
* { id, label, type, required, secretRefOnly, sourceNodeId, workflowId }
|
|
315
|
+
*
|
|
316
|
+
* Used by the metadata store + workflow sidecar so the manual run input
|
|
317
|
+
* schema is a single typed contract — never re-derived inside UI code.
|
|
318
|
+
*
|
|
319
|
+
* Existing redaction + size limits remain enforced by
|
|
320
|
+
* `validateRunInputsEnvelope` / `normalizeRunInputsEnvelope`. This helper
|
|
321
|
+
* is descriptor-only and does not mutate or echo any field value.
|
|
322
|
+
*/
|
|
323
|
+
function describeRunInputMetadataItems({ workflowId, graph, objectId, rowId } = {}) {
|
|
324
|
+
const schema = discoverRunInputSchema(graph);
|
|
325
|
+
const fields = Array.isArray(schema?.fields) ? schema.fields : [];
|
|
326
|
+
const workflowKey = String(workflowId || (objectId && rowId ? `${objectId}::${rowId}` : "")).trim();
|
|
327
|
+
const sourceNodeId = findFirstHumanInputNodeId(graph);
|
|
328
|
+
return fields.map((field) => ({
|
|
329
|
+
kind: "workspaceRunInput",
|
|
330
|
+
id: field.id,
|
|
331
|
+
label: field.label,
|
|
332
|
+
type: field.type,
|
|
333
|
+
required: Boolean(field.required),
|
|
334
|
+
isSecret: Boolean(field.isSecret),
|
|
335
|
+
secretRefOnly: Boolean(field.isSecret) || field.type === "secretRef",
|
|
336
|
+
sourceNodeId,
|
|
337
|
+
workflowId: workflowKey
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function findFirstHumanInputNodeId(graphValue) {
|
|
342
|
+
const graph = parseOrchestrationGraph(graphValue) || graphValue || null;
|
|
343
|
+
const nodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
|
344
|
+
for (const node of nodes) {
|
|
345
|
+
const type = String(node?.type || "").trim();
|
|
346
|
+
const action = String(node?.config?.action || "").trim();
|
|
347
|
+
if (type === "human-input" || action === "form") return String(node?.id || "").trim() || "human-input";
|
|
348
|
+
}
|
|
349
|
+
return "";
|
|
350
|
+
}
|
|
351
|
+
|
|
310
352
|
export {
|
|
311
353
|
RUN_INPUTS_KIND,
|
|
312
354
|
MAX_RUN_INPUT_VALUES,
|
|
@@ -319,5 +361,6 @@ export {
|
|
|
319
361
|
summarizeRunInputs,
|
|
320
362
|
validateRunInputsEnvelope,
|
|
321
363
|
buildInputPayloadForRunner,
|
|
364
|
+
describeRunInputMetadataItems,
|
|
322
365
|
parseFieldDescriptor
|
|
323
366
|
};
|
|
@@ -60,7 +60,9 @@ function parseSandboxRunTrace(lastResponse) {
|
|
|
60
60
|
envRefsMissing: Array.isArray(parsed.envRefsMissing) ? parsed.envRefsMissing : [],
|
|
61
61
|
input: safeInput,
|
|
62
62
|
inputSummary: safeInput ? summarizeRunInputs(safeInput) : null,
|
|
63
|
-
exports: parsed.exports && typeof parsed.exports === "object" ? parsed.exports : null
|
|
63
|
+
exports: parsed.exports && typeof parsed.exports === "object" ? parsed.exports : null,
|
|
64
|
+
swarm: parsed.swarm && typeof parsed.swarm === "object" ? parsed.swarm : null,
|
|
65
|
+
logTree: Array.isArray(parsed.logTree) ? parsed.logTree : null
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -585,6 +585,41 @@ function notFoundError(message) {
|
|
|
585
585
|
return error;
|
|
586
586
|
}
|
|
587
587
|
|
|
588
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
589
|
+
// Workspace Metadata Graph V1 — safe agent host readiness metadata.
|
|
590
|
+
//
|
|
591
|
+
// Pure helper that distills a sandbox row into the safe metadata the
|
|
592
|
+
// workspace metadata graph + UI inspector can show. Reads ONLY the safe,
|
|
593
|
+
// allowlisted readiness fields the auth helper persists on the row. Never
|
|
594
|
+
// echoes raw tokens, login URLs, or stdout/stderr — those live inside the
|
|
595
|
+
// helper's response object, not the row patch.
|
|
596
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
597
|
+
|
|
598
|
+
function describeAgentHostReadinessMetadata(row) {
|
|
599
|
+
if (!row || typeof row !== "object") return null;
|
|
600
|
+
const adapter = String(row?.adapter || "").trim();
|
|
601
|
+
const agentHost = String(row?.agentHost || "").trim();
|
|
602
|
+
const runLocality = String(row?.runLocality || "").trim();
|
|
603
|
+
const safe = {};
|
|
604
|
+
for (const key of SAFE_ROW_PATCH_FIELDS) {
|
|
605
|
+
if (Object.prototype.hasOwnProperty.call(row, key)) {
|
|
606
|
+
safe[key] = row[key];
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
kind: "workspaceAgentHostReadiness",
|
|
611
|
+
adapter,
|
|
612
|
+
agentHost,
|
|
613
|
+
runLocality,
|
|
614
|
+
status: KNOWN_AGENT_AUTH_STATUSES.includes(safe.agentAuthStatus) ? safe.agentAuthStatus : "unknown",
|
|
615
|
+
provider: String(safe.agentAuthProvider || agentHost || "unknown").trim(),
|
|
616
|
+
lastChecked: String(safe.agentAuthLastChecked || "").trim(),
|
|
617
|
+
lastExitCode: typeof safe.agentAuthLastExitCode === "number" ? safe.agentAuthLastExitCode : null,
|
|
618
|
+
lastMessage: String(safe.agentAuthLastMessage || "").trim(),
|
|
619
|
+
lastLoginUrl: String(safe.agentAuthLastLoginUrl || "").trim()
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
588
623
|
// ──────────────────────────────────────────────────────────────────────────
|
|
589
624
|
// Backwards-compatible Claude aliases (legacy)
|
|
590
625
|
// ──────────────────────────────────────────────────────────────────────────
|
|
@@ -616,6 +651,7 @@ export {
|
|
|
616
651
|
buildRowPatch,
|
|
617
652
|
checkAgentStatus,
|
|
618
653
|
checkClaudeStatus,
|
|
654
|
+
describeAgentHostReadinessMetadata,
|
|
619
655
|
findSandboxRow,
|
|
620
656
|
getAgentHostCapabilities,
|
|
621
657
|
redactSecrets,
|
|
@@ -529,6 +529,58 @@ function deriveChartHydrationState({
|
|
|
529
529
|
return "computed";
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Workspace Metadata Graph V1 — typed widget dependency contract.
|
|
534
|
+
*
|
|
535
|
+
* Returns the same dependency shape Twenty-style sidecars consume: required
|
|
536
|
+
* axis fields, filter fields, sort fields, aggregation fields, output shape,
|
|
537
|
+
* and per-widget warnings. The metadata store calls a richer derivation that
|
|
538
|
+
* carries the bound object id; this helper accepts a single widget config
|
|
539
|
+
* and returns the dependency contract without needing the full store.
|
|
540
|
+
*
|
|
541
|
+
* Pure. No fetch. No mutation. Never throws on partial widget shapes.
|
|
542
|
+
*/
|
|
543
|
+
function deriveWidgetDependencyContract(widget) {
|
|
544
|
+
const config = isPlainObject(widget?.config) ? widget.config : {};
|
|
545
|
+
const binding = isPlainObject(config.binding) ? config.binding : null;
|
|
546
|
+
const xAxis = isPlainObject(config.xAxis) ? config.xAxis : null;
|
|
547
|
+
const yAxis = isPlainObject(config.yAxis) ? config.yAxis : null;
|
|
548
|
+
const filter = isPlainObject(config.filter) ? config.filter : null;
|
|
549
|
+
const xField = typeof xAxis?.field === "string" ? xAxis.field.trim() : "";
|
|
550
|
+
const yField = typeof yAxis?.field === "string" ? yAxis.field.trim() : "";
|
|
551
|
+
const groupField = typeof yAxis?.groupBy === "string" ? yAxis.groupBy.trim() : "";
|
|
552
|
+
const operation = typeof yAxis?.operation === "string"
|
|
553
|
+
? yAxis.operation.trim()
|
|
554
|
+
: (typeof yAxis?.aggregation === "string" ? yAxis.aggregation.trim() : "sum");
|
|
555
|
+
const clauses = Array.isArray(filter?.clauses) ? filter.clauses : [];
|
|
556
|
+
const filterFields = [];
|
|
557
|
+
for (const clause of clauses) {
|
|
558
|
+
if (!isPlainObject(clause)) continue;
|
|
559
|
+
const field = typeof clause.fieldId === "string" ? clause.fieldId.trim() : "";
|
|
560
|
+
if (field && !filterFields.includes(field)) filterFields.push(field);
|
|
561
|
+
}
|
|
562
|
+
const sortFields = xField ? [xField] : [];
|
|
563
|
+
const aggregationFields = yField ? [yField] : [];
|
|
564
|
+
const required = Array.from(new Set([xField, yField, groupField].filter(Boolean)));
|
|
565
|
+
const widgetKind = typeof widget?.kind === "string" ? widget.kind.trim() : "chart";
|
|
566
|
+
const warnings = [];
|
|
567
|
+
if (widgetKind === "chart" && required.length === 0 && operation !== "count" && operation !== "countAll") {
|
|
568
|
+
warnings.push("Chart widget is missing both X and Y axis fields.");
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
objectId: typeof binding?.objectId === "string" ? binding.objectId.trim() : "",
|
|
572
|
+
sourceType: typeof binding?.sourceType === "string" ? binding.sourceType.trim() : "",
|
|
573
|
+
sourceAuthority: "workspace-config",
|
|
574
|
+
required,
|
|
575
|
+
filter: filterFields,
|
|
576
|
+
sort: sortFields,
|
|
577
|
+
aggregation: aggregationFields,
|
|
578
|
+
operation: operation || "sum",
|
|
579
|
+
outputShape: widgetKind === "chart" ? "number[]" : "row[]",
|
|
580
|
+
warnings
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
532
584
|
export {
|
|
533
585
|
KNOWN_AGGREGATIONS,
|
|
534
586
|
applyFilter,
|
|
@@ -538,5 +590,6 @@ export {
|
|
|
538
590
|
computeChartProjectionDebug,
|
|
539
591
|
computeChartValuesFromRows,
|
|
540
592
|
deriveChartHydrationState,
|
|
593
|
+
deriveWidgetDependencyContract,
|
|
541
594
|
groupRows
|
|
542
595
|
};
|