@growthub/cli 0.13.9 → 0.14.1
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/env-status/route.js +31 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +227 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/query/route.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +70 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceActivationPanel.jsx +17 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +6 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +61 -35
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryCreationCockpit.jsx +200 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +414 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +339 -77
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +81 -10
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +70 -85
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ReferencePicker.jsx +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SidecarExpandView.jsx +37 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SwarmRunCockpit.jsx +625 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +150 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +229 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +224 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +2 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-agent-host.js +139 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-intelligence.js +4 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-registry-creation-flow.js +317 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-response-profile.js +207 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/creation-error-recovery.js +103 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/env-status.js +100 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +246 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +69 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +411 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +215 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-resolver-write.js +67 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/serverless-upgrade.js +89 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +11 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +8 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +30 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +8 -6
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-resolver-proposal.js +200 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-swarm-proposal.js +551 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -1
- package/package.json +1 -1
package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/env-status.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Env Status V1 — the honest, secret-safe "which referenced env keys actually
|
|
3
|
+
* resolve right now" signal.
|
|
4
|
+
*
|
|
5
|
+
* The creation cockpit (api-registry drawer) cannot read process.env in the
|
|
6
|
+
* browser, so auth readiness must come from a server signal. This module is the
|
|
7
|
+
* pure core of `GET /api/workspace/env-status`: given the governed config and
|
|
8
|
+
* the runtime environment it returns the set of *referenced* auth/env ref slugs
|
|
9
|
+
* whose candidate keys resolve to a value — slugs only, never a value.
|
|
10
|
+
*
|
|
11
|
+
* Pure + env-injectable so it is deterministically testable.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describePostgresAdapter } from "./adapters/persistence/postgres.js";
|
|
15
|
+
import { describeQstashKvAdapter } from "./adapters/persistence/qstash-kv.js";
|
|
16
|
+
import { describeProviderManagedAdapter } from "./adapters/persistence/provider-managed.js";
|
|
17
|
+
|
|
18
|
+
function clean(value) {
|
|
19
|
+
return String(value == null ? "" : value).trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Canonical UPPER_SNAKE candidate expansion for a logical ref. */
|
|
23
|
+
function envKeyCandidates(ref) {
|
|
24
|
+
const token = clean(ref).replace(/[^a-z0-9]+/gi, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
25
|
+
if (!token) return [];
|
|
26
|
+
return Array.from(new Set([token, `${token}_API_KEY`, `${token}_TOKEN`]));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Collect every auth/env ref slug referenced by the governed config:
|
|
31
|
+
* - api-registry rows: authRef
|
|
32
|
+
* - data-source rows: authRef
|
|
33
|
+
* - sandbox-environment rows: envRefs (comma-separated)
|
|
34
|
+
* Returns the original ref strings (deduped), preserving the operator's casing
|
|
35
|
+
* so the cockpit can match them against a registry row's authRef.
|
|
36
|
+
*/
|
|
37
|
+
function collectReferencedRefs(workspaceConfig) {
|
|
38
|
+
const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
|
|
39
|
+
const refs = new Set();
|
|
40
|
+
for (const object of objects) {
|
|
41
|
+
const rows = Array.isArray(object?.rows) ? object.rows : [];
|
|
42
|
+
if (object?.objectType === "api-registry" || object?.objectType === "data-source") {
|
|
43
|
+
for (const row of rows) {
|
|
44
|
+
const ref = clean(row?.authRef);
|
|
45
|
+
if (ref) refs.add(ref);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (object?.objectType === "sandbox-environment") {
|
|
49
|
+
for (const row of rows) {
|
|
50
|
+
for (const part of clean(row?.envRefs).split(",")) {
|
|
51
|
+
const ref = clean(part);
|
|
52
|
+
if (ref) refs.add(ref);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return Array.from(refs);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Return the referenced refs whose candidate keys resolve in `env`.
|
|
62
|
+
* `env` is injectable (defaults to process.env). Never returns a value.
|
|
63
|
+
*/
|
|
64
|
+
function computeConfiguredEnvRefs(workspaceConfig, env = process.env) {
|
|
65
|
+
const source = env && typeof env === "object" ? env : {};
|
|
66
|
+
const resolves = (ref) => envKeyCandidates(ref).some((key) => Boolean(source[key]));
|
|
67
|
+
return collectReferencedRefs(workspaceConfig).filter(resolves);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Persistence/serverless adapter env-readiness — single-sourced from the real
|
|
72
|
+
* thin-adapter descriptors (postgres / qstash-kv / provider-managed). These are
|
|
73
|
+
* the durable-runtime layers a serverless workflow needs; the cockpit surfaces
|
|
74
|
+
* exactly which are env-ready so "make this workflow persistent + scheduled"
|
|
75
|
+
* has an honest, actionable signal. Slugs/booleans only — never a value.
|
|
76
|
+
*/
|
|
77
|
+
function listPersistenceAdapterReadiness(env = process.env) {
|
|
78
|
+
const source = env && typeof env === "object" ? env : {};
|
|
79
|
+
const descriptors = [describePostgresAdapter(), describeQstashKvAdapter(), describeProviderManagedAdapter()];
|
|
80
|
+
return descriptors.map((d) => {
|
|
81
|
+
const requiredEnv = Array.isArray(d.requiredEnv) ? d.requiredEnv : [];
|
|
82
|
+
const missingEnv = requiredEnv.filter((k) => !source[k]);
|
|
83
|
+
return {
|
|
84
|
+
id: d.id,
|
|
85
|
+
label: d.label,
|
|
86
|
+
mode: d.mode,
|
|
87
|
+
requiredEnv,
|
|
88
|
+
// provider-managed needs no env (the deploy provider owns persistence).
|
|
89
|
+
configured: requiredEnv.length === 0 ? true : missingEnv.length === 0,
|
|
90
|
+
missingEnv,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
envKeyCandidates,
|
|
97
|
+
collectReferencedRefs,
|
|
98
|
+
computeConfiguredEnvRefs,
|
|
99
|
+
listPersistenceAdapterReadiness,
|
|
100
|
+
};
|
|
@@ -63,6 +63,44 @@ const MAX_SUBAGENT_OUTPUT_FOR_SYNTH = 4096;
|
|
|
63
63
|
const PROMPT_CAPABLE_ADAPTERS = new Set(["local-agent-host", "local-intelligence"]);
|
|
64
64
|
const OUTCOME_SCORE_RE = /OUTCOME_SCORE\s*[:=]\s*([01](?:\.\d+)?|\.\d+)/i;
|
|
65
65
|
|
|
66
|
+
function cleanEventString(value) {
|
|
67
|
+
return redactSecretsFromText(String(value ?? "").trim());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function emitSwarmDelta(executionContext, payload) {
|
|
71
|
+
const emit = executionContext?.onEvent;
|
|
72
|
+
if (typeof emit !== "function") return;
|
|
73
|
+
try {
|
|
74
|
+
emit({
|
|
75
|
+
kind: "growthub-sandbox-run-delta-v1",
|
|
76
|
+
emittedAt: new Date().toISOString(),
|
|
77
|
+
runId: executionContext.runId || "",
|
|
78
|
+
...payload
|
|
79
|
+
});
|
|
80
|
+
} catch {
|
|
81
|
+
// Observability is best-effort; execution must not fail because the
|
|
82
|
+
// browser disconnected or a cosmetic stream consumer rejected an event.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function taskDeltaFromResult(task) {
|
|
87
|
+
return {
|
|
88
|
+
taskId: cleanEventString(task?.taskId || task?.nodeId || ""),
|
|
89
|
+
nodeId: cleanEventString(task?.nodeId || task?.taskId || ""),
|
|
90
|
+
role: cleanEventString(task?.role || "Subagent"),
|
|
91
|
+
phaseId: cleanEventString(task?.phaseId || "dispatch") || "dispatch",
|
|
92
|
+
status: cleanEventString(task?.status || "unknown") || "unknown",
|
|
93
|
+
adapter: cleanEventString(task?.adapter || ""),
|
|
94
|
+
agentHost: cleanEventString(task?.agentHost || ""),
|
|
95
|
+
durationMs: Number.isFinite(Number(task?.durationMs)) ? Number(task.durationMs) : 0,
|
|
96
|
+
tokens: task?.tokens ?? null,
|
|
97
|
+
tools: task?.tools ?? null,
|
|
98
|
+
startedAt: cleanEventString(task?.startedAt || ""),
|
|
99
|
+
endedAt: cleanEventString(task?.endedAt || ""),
|
|
100
|
+
error: cleanEventString(task?.error || "")
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
66
104
|
function clampPositiveInt(value, fallback) {
|
|
67
105
|
const n = Number(value);
|
|
68
106
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
@@ -114,6 +152,21 @@ function chooseAdapterIdForSubagent({ subagentConfig, fallbackAdapterId, fallbac
|
|
|
114
152
|
};
|
|
115
153
|
}
|
|
116
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Truthful telemetry extraction (SWARM_RUN_CONTRACT_V1). Tokens / tool counts
|
|
157
|
+
* come ONLY from the adapter's reported metadata — when the adapter does not
|
|
158
|
+
* report a number the value is null, never an estimate.
|
|
159
|
+
*/
|
|
160
|
+
function extractAdapterTelemetry(result) {
|
|
161
|
+
const meta = result?.adapterMeta && typeof result.adapterMeta === "object" ? result.adapterMeta : {};
|
|
162
|
+
const tokens = meta.tokens == null || meta.tokens === "" ? null : Number(meta.tokens);
|
|
163
|
+
const tools = meta.tools == null || meta.tools === "" ? null : Number(meta.tools);
|
|
164
|
+
return {
|
|
165
|
+
tokens: Number.isFinite(tokens) && tokens >= 0 ? tokens : null,
|
|
166
|
+
tools: Number.isFinite(tools) && tools >= 0 ? tools : null
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
117
170
|
function describeSubagent(node) {
|
|
118
171
|
const cfg = node?.config || {};
|
|
119
172
|
const role = String(cfg.role || node?.label || node?.id || "subagent").trim();
|
|
@@ -240,7 +293,8 @@ async function runThroughAdapter({
|
|
|
240
293
|
envRefsMissing,
|
|
241
294
|
runId,
|
|
242
295
|
name,
|
|
243
|
-
ranAt
|
|
296
|
+
ranAt,
|
|
297
|
+
intelligence
|
|
244
298
|
}) {
|
|
245
299
|
const adapter = getSandboxAdapter(adapterId);
|
|
246
300
|
if (!adapter) {
|
|
@@ -281,7 +335,20 @@ async function runThroughAdapter({
|
|
|
281
335
|
envRefSlugs,
|
|
282
336
|
envRefsMissing,
|
|
283
337
|
workdir,
|
|
284
|
-
ranAt: ranAt || new Date(startedAt).toISOString()
|
|
338
|
+
ranAt: ranAt || new Date(startedAt).toISOString(),
|
|
339
|
+
// local-intelligence speaks the intelligenceSandbox envelope — the
|
|
340
|
+
// phase prompt travels as userIntent; model/endpoint settings come
|
|
341
|
+
// from the governed row (slugs/URLs only, never secret values).
|
|
342
|
+
...(adapterId === "local-intelligence"
|
|
343
|
+
? {
|
|
344
|
+
intelligenceSandbox: {
|
|
345
|
+
userIntent: command,
|
|
346
|
+
localModel: String(intelligence?.localModel || "").trim(),
|
|
347
|
+
localEndpoint: String(intelligence?.localEndpoint || "").trim(),
|
|
348
|
+
intelligenceAdapterMode: String(intelligence?.intelligenceAdapterMode || "ollama").trim() || "ollama"
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
: {})
|
|
285
352
|
});
|
|
286
353
|
} catch (error) {
|
|
287
354
|
return {
|
|
@@ -324,6 +391,7 @@ async function runOrchestratorPhase({ orchestratorNode, subagents, inputPayload,
|
|
|
324
391
|
const command = buildOrchestratorCommand({ orchestratorNode, subagents, inputPayload });
|
|
325
392
|
const env = { ...(executionContext.env || {}), GROWTHUB_SWARM_PHASE: "orchestrator" };
|
|
326
393
|
const startedAt = Date.now();
|
|
394
|
+
const startedAtIso = new Date(startedAt).toISOString();
|
|
327
395
|
const result = await runThroughAdapter({
|
|
328
396
|
adapterId: resolved.adapterId,
|
|
329
397
|
agentHost: resolved.agentHost,
|
|
@@ -335,11 +403,13 @@ async function runOrchestratorPhase({ orchestratorNode, subagents, inputPayload,
|
|
|
335
403
|
env,
|
|
336
404
|
envRefSlugs: executionContext.envRefSlugs || [],
|
|
337
405
|
envRefsMissing: executionContext.envRefsMissing || [],
|
|
406
|
+
intelligence: executionContext.intelligence,
|
|
338
407
|
runId: `${executionContext.runId}_orchestrator`,
|
|
339
408
|
name: `${executionContext.sandboxName || "swarm"}::orchestrator`
|
|
340
409
|
});
|
|
341
410
|
const stdout = redactSecretsFromText(result?.stdout || "");
|
|
342
411
|
const errorText = redactSecretsFromText(result?.error || "");
|
|
412
|
+
const telemetry = extractAdapterTelemetry(result);
|
|
343
413
|
return {
|
|
344
414
|
status: result?.ok === true && !errorText ? "completed" : "failed",
|
|
345
415
|
error: errorText,
|
|
@@ -349,6 +419,11 @@ async function runOrchestratorPhase({ orchestratorNode, subagents, inputPayload,
|
|
|
349
419
|
output: stdout,
|
|
350
420
|
stderr: redactSecretsFromText(result?.stderr || ""),
|
|
351
421
|
plan: stdout,
|
|
422
|
+
tokens: telemetry.tokens,
|
|
423
|
+
tools: telemetry.tools,
|
|
424
|
+
startedAt: startedAtIso,
|
|
425
|
+
endedAt: new Date().toISOString(),
|
|
426
|
+
phaseId: "plan",
|
|
352
427
|
adapterMeta: { ...(result?.adapterMeta || {}), swarmPhase: "orchestrator" }
|
|
353
428
|
};
|
|
354
429
|
}
|
|
@@ -364,6 +439,10 @@ async function dispatchSubagentTask({
|
|
|
364
439
|
const required = subagentConfig.required !== false;
|
|
365
440
|
const taskId = subagentNode?.id || `task-${taskIndex + 1}`;
|
|
366
441
|
const role = String(subagentConfig.role || subagentNode?.label || "subagent");
|
|
442
|
+
// Author-named phase (config.phase / config.phaseId) travels onto the task
|
|
443
|
+
// so the record projection groups under the same phases the declared
|
|
444
|
+
// skeleton shows. Absent → "dispatch", exactly as before.
|
|
445
|
+
const phaseId = String(subagentConfig.phase || subagentConfig.phaseId || "").trim().toLowerCase() || "dispatch";
|
|
367
446
|
|
|
368
447
|
const resolved = chooseAdapterIdForSubagent({
|
|
369
448
|
subagentConfig,
|
|
@@ -371,6 +450,7 @@ async function dispatchSubagentTask({
|
|
|
371
450
|
fallbackAgentHost: executionContext.agentHost
|
|
372
451
|
});
|
|
373
452
|
if (!resolved.adapterId || resolved.error) {
|
|
453
|
+
const gateAt = new Date().toISOString();
|
|
374
454
|
return {
|
|
375
455
|
taskId,
|
|
376
456
|
nodeId: taskId,
|
|
@@ -383,6 +463,11 @@ async function dispatchSubagentTask({
|
|
|
383
463
|
stdout: "",
|
|
384
464
|
stderr: "",
|
|
385
465
|
error: resolved.error || "no prompt-capable adapter resolved for subagent",
|
|
466
|
+
tokens: null,
|
|
467
|
+
tools: null,
|
|
468
|
+
startedAt: gateAt,
|
|
469
|
+
endedAt: gateAt,
|
|
470
|
+
phaseId,
|
|
386
471
|
adapterMeta: { swarmSubagent: true, reason: "adapter-gate" }
|
|
387
472
|
};
|
|
388
473
|
}
|
|
@@ -407,11 +492,13 @@ async function dispatchSubagentTask({
|
|
|
407
492
|
env,
|
|
408
493
|
envRefSlugs: executionContext.envRefSlugs || [],
|
|
409
494
|
envRefsMissing: executionContext.envRefsMissing || [],
|
|
495
|
+
intelligence: executionContext.intelligence,
|
|
410
496
|
runId: `${executionContext.runId}_${taskId}`,
|
|
411
497
|
name: `${executionContext.sandboxName || "swarm"}::${taskId}`
|
|
412
498
|
});
|
|
413
499
|
const errorText = redactSecretsFromText(result?.error || "");
|
|
414
500
|
const ok = result?.ok === true && !errorText;
|
|
501
|
+
const telemetry = extractAdapterTelemetry(result);
|
|
415
502
|
return {
|
|
416
503
|
taskId,
|
|
417
504
|
nodeId: taskId,
|
|
@@ -425,6 +512,11 @@ async function dispatchSubagentTask({
|
|
|
425
512
|
stdout: redactSecretsFromText(result?.stdout || ""),
|
|
426
513
|
stderr: redactSecretsFromText(result?.stderr || ""),
|
|
427
514
|
error: errorText,
|
|
515
|
+
tokens: telemetry.tokens,
|
|
516
|
+
tools: telemetry.tools,
|
|
517
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
518
|
+
endedAt: new Date().toISOString(),
|
|
519
|
+
phaseId,
|
|
428
520
|
adapterMeta: { ...(result?.adapterMeta || {}), swarmSubagent: true }
|
|
429
521
|
};
|
|
430
522
|
}
|
|
@@ -467,6 +559,7 @@ async function runSynthesisPhase({ synthesisNode, swarmConfig, tasks, inputPaylo
|
|
|
467
559
|
const command = buildSynthesisCommand({ synthesisNode, swarmConfig, tasks, inputPayload });
|
|
468
560
|
const env = { ...(executionContext.env || {}), GROWTHUB_SWARM_PHASE: "synthesis" };
|
|
469
561
|
const startedAt = Date.now();
|
|
562
|
+
const startedAtIso = new Date(startedAt).toISOString();
|
|
470
563
|
const result = await runThroughAdapter({
|
|
471
564
|
adapterId: resolved.adapterId,
|
|
472
565
|
agentHost: resolved.agentHost,
|
|
@@ -478,6 +571,7 @@ async function runSynthesisPhase({ synthesisNode, swarmConfig, tasks, inputPaylo
|
|
|
478
571
|
env,
|
|
479
572
|
envRefSlugs: executionContext.envRefSlugs || [],
|
|
480
573
|
envRefsMissing: executionContext.envRefsMissing || [],
|
|
574
|
+
intelligence: executionContext.intelligence,
|
|
481
575
|
runId: `${executionContext.runId}_synthesis`,
|
|
482
576
|
name: `${executionContext.sandboxName || "swarm"}::synthesis`
|
|
483
577
|
});
|
|
@@ -485,6 +579,7 @@ async function runSynthesisPhase({ synthesisNode, swarmConfig, tasks, inputPaylo
|
|
|
485
579
|
const errorText = redactSecretsFromText(result?.error || "");
|
|
486
580
|
const match = stdout.match(OUTCOME_SCORE_RE);
|
|
487
581
|
const parsedOutcomeScore = match ? clamp01(match[1]) : null;
|
|
582
|
+
const telemetry = extractAdapterTelemetry(result);
|
|
488
583
|
return {
|
|
489
584
|
status: result?.ok === true && !errorText ? "completed" : "failed",
|
|
490
585
|
ranSynthesis: true,
|
|
@@ -495,6 +590,11 @@ async function runSynthesisPhase({ synthesisNode, swarmConfig, tasks, inputPaylo
|
|
|
495
590
|
adapter: resolved.adapterId,
|
|
496
591
|
agentHost: resolved.agentHost || "",
|
|
497
592
|
parsedOutcomeScore,
|
|
593
|
+
tokens: telemetry.tokens,
|
|
594
|
+
tools: telemetry.tools,
|
|
595
|
+
startedAt: startedAtIso,
|
|
596
|
+
endedAt: new Date().toISOString(),
|
|
597
|
+
phaseId: "synthesize",
|
|
498
598
|
adapterMeta: { ...(result?.adapterMeta || {}), swarmPhase: "synthesis" }
|
|
499
599
|
};
|
|
500
600
|
}
|
|
@@ -723,18 +823,63 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
723
823
|
networkAllow: executionContext?.networkAllow === true,
|
|
724
824
|
allowList: executionContext?.allowList || [],
|
|
725
825
|
timeoutMs: clampPositiveInt(timeoutMs, DEFAULT_SUBAGENT_TIMEOUT_MS),
|
|
726
|
-
sandboxName: executionContext?.sandboxName || row?.Name || "swarm"
|
|
826
|
+
sandboxName: executionContext?.sandboxName || row?.Name || "swarm",
|
|
827
|
+
onEvent: executionContext?.onEvent,
|
|
828
|
+
// Row-configured local-intelligence settings (model id, endpoint URL,
|
|
829
|
+
// adapter mode) — configuration only, never secret values.
|
|
830
|
+
intelligence: {
|
|
831
|
+
localModel: String(row?.localModel || "").trim(),
|
|
832
|
+
localEndpoint: String(row?.localEndpoint || "").trim(),
|
|
833
|
+
intelligenceAdapterMode: String(row?.intelligenceAdapterMode || "").trim() || "ollama"
|
|
834
|
+
}
|
|
727
835
|
};
|
|
728
836
|
|
|
729
837
|
const startedAt = Date.now();
|
|
838
|
+
emitSwarmDelta(ctx, {
|
|
839
|
+
type: "swarm.run.started",
|
|
840
|
+
status: "running",
|
|
841
|
+
name: cleanEventString(ctx.sandboxName),
|
|
842
|
+
adapter: cleanEventString(ctx.adapterId),
|
|
843
|
+
agentHost: cleanEventString(ctx.agentHost),
|
|
844
|
+
maxConcurrency,
|
|
845
|
+
phaseCount: 3,
|
|
846
|
+
taskCount: subagents.length
|
|
847
|
+
});
|
|
730
848
|
|
|
731
849
|
// Phase 1: Plan ----------------------------------------------------------
|
|
850
|
+
emitSwarmDelta(ctx, {
|
|
851
|
+
type: "swarm.phase.started",
|
|
852
|
+
phaseId: "plan",
|
|
853
|
+
label: "Plan",
|
|
854
|
+
status: "running"
|
|
855
|
+
});
|
|
732
856
|
const orchestratorResult = await runOrchestratorPhase({
|
|
733
857
|
orchestratorNode: orchestrator,
|
|
734
858
|
subagents,
|
|
735
859
|
inputPayload: manualPayload,
|
|
736
860
|
executionContext: ctx
|
|
737
861
|
});
|
|
862
|
+
emitSwarmDelta(ctx, {
|
|
863
|
+
type: orchestratorResult.status === "failed" ? "swarm.phase.failed" : "swarm.phase.completed",
|
|
864
|
+
phaseId: "plan",
|
|
865
|
+
label: "Plan",
|
|
866
|
+
status: orchestratorResult.status,
|
|
867
|
+
agent: {
|
|
868
|
+
taskId: cleanEventString(orchestrator.id || "orchestrator"),
|
|
869
|
+
nodeId: cleanEventString(orchestrator.id || "orchestrator"),
|
|
870
|
+
role: cleanEventString(orchestrator?.config?.role || "Orchestrator"),
|
|
871
|
+
phaseId: "plan",
|
|
872
|
+
status: orchestratorResult.status,
|
|
873
|
+
adapter: cleanEventString(orchestratorResult.adapter || ""),
|
|
874
|
+
agentHost: cleanEventString(orchestratorResult.agentHost || ""),
|
|
875
|
+
durationMs: Number(orchestratorResult.durationMs) || 0,
|
|
876
|
+
tokens: orchestratorResult.tokens ?? null,
|
|
877
|
+
tools: orchestratorResult.tools ?? null,
|
|
878
|
+
startedAt: cleanEventString(orchestratorResult.startedAt || ""),
|
|
879
|
+
endedAt: cleanEventString(orchestratorResult.endedAt || ""),
|
|
880
|
+
error: cleanEventString(orchestratorResult.error || "")
|
|
881
|
+
}
|
|
882
|
+
});
|
|
738
883
|
|
|
739
884
|
if (orchestratorResult.status === "failed" && String(orchestratorResult.error || "").length > 0) {
|
|
740
885
|
const durationMs = Date.now() - startedAt;
|
|
@@ -747,6 +892,13 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
747
892
|
outcomeOk: false,
|
|
748
893
|
synthesisResult: null
|
|
749
894
|
});
|
|
895
|
+
emitSwarmDelta(ctx, {
|
|
896
|
+
type: "swarm.run.failed",
|
|
897
|
+
status: "failed",
|
|
898
|
+
phaseFailed: "plan",
|
|
899
|
+
durationMs,
|
|
900
|
+
error: cleanEventString(orchestratorResult.error || "")
|
|
901
|
+
});
|
|
750
902
|
return {
|
|
751
903
|
ok: false,
|
|
752
904
|
exitCode: 1,
|
|
@@ -790,23 +942,68 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
790
942
|
// Phase 2: Dispatch ------------------------------------------------------
|
|
791
943
|
let observedParallelism = 0;
|
|
792
944
|
let activeNow = 0;
|
|
945
|
+
emitSwarmDelta(ctx, {
|
|
946
|
+
type: "swarm.phase.started",
|
|
947
|
+
phaseId: "dispatch",
|
|
948
|
+
label: "Dispatch",
|
|
949
|
+
status: "running",
|
|
950
|
+
taskCount: subagents.length
|
|
951
|
+
});
|
|
793
952
|
const tasks = await runSubagentsWithConcurrency(subagents, maxConcurrency, async (subagentNode, index) => {
|
|
794
953
|
activeNow += 1;
|
|
795
954
|
if (activeNow > observedParallelism) observedParallelism = activeNow;
|
|
955
|
+
const subagentConfig = subagentNode?.config || {};
|
|
956
|
+
const taskId = subagentNode?.id || `task-${index + 1}`;
|
|
957
|
+
const phaseId = String(subagentConfig.phase || subagentConfig.phaseId || "").trim().toLowerCase() || "dispatch";
|
|
958
|
+
emitSwarmDelta(ctx, {
|
|
959
|
+
type: "swarm.task.started",
|
|
960
|
+
phaseId,
|
|
961
|
+
taskId: cleanEventString(taskId),
|
|
962
|
+
nodeId: cleanEventString(taskId),
|
|
963
|
+
role: cleanEventString(subagentConfig.role || subagentNode?.label || `Agent ${index + 1}`),
|
|
964
|
+
status: "running",
|
|
965
|
+
active: activeNow,
|
|
966
|
+
observedParallelism
|
|
967
|
+
});
|
|
796
968
|
try {
|
|
797
|
-
|
|
969
|
+
const task = await dispatchSubagentTask({
|
|
798
970
|
subagentNode,
|
|
799
971
|
orchestratorPlan: orchestratorResult.plan,
|
|
800
972
|
inputPayload: manualPayload,
|
|
801
973
|
executionContext: ctx,
|
|
802
974
|
taskIndex: index
|
|
803
975
|
});
|
|
976
|
+
emitSwarmDelta(ctx, {
|
|
977
|
+
type: task.status === "completed" ? "swarm.task.completed" : "swarm.task.failed",
|
|
978
|
+
phaseId: task.phaseId || phaseId,
|
|
979
|
+
task: taskDeltaFromResult(task)
|
|
980
|
+
});
|
|
981
|
+
return task;
|
|
804
982
|
} finally {
|
|
805
983
|
activeNow -= 1;
|
|
806
984
|
}
|
|
807
985
|
});
|
|
986
|
+
const dispatchStatus = tasks.length > 0 && tasks.every((t) => t.status === "completed")
|
|
987
|
+
? "completed"
|
|
988
|
+
: tasks.some((t) => t.status === "failed")
|
|
989
|
+
? "failed"
|
|
990
|
+
: "completed";
|
|
991
|
+
emitSwarmDelta(ctx, {
|
|
992
|
+
type: dispatchStatus === "failed" ? "swarm.phase.failed" : "swarm.phase.completed",
|
|
993
|
+
phaseId: "dispatch",
|
|
994
|
+
label: "Dispatch",
|
|
995
|
+
status: dispatchStatus,
|
|
996
|
+
taskCount: tasks.length,
|
|
997
|
+
observedParallelism
|
|
998
|
+
});
|
|
808
999
|
|
|
809
1000
|
// Phase 3: Synthesize ----------------------------------------------------
|
|
1001
|
+
emitSwarmDelta(ctx, {
|
|
1002
|
+
type: "swarm.phase.started",
|
|
1003
|
+
phaseId: "synthesize",
|
|
1004
|
+
label: "Synthesize",
|
|
1005
|
+
status: "running"
|
|
1006
|
+
});
|
|
810
1007
|
const synthesisResult = await runSynthesisPhase({
|
|
811
1008
|
synthesisNode: synthesis,
|
|
812
1009
|
swarmConfig,
|
|
@@ -814,6 +1011,29 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
814
1011
|
inputPayload: manualPayload,
|
|
815
1012
|
executionContext: ctx
|
|
816
1013
|
});
|
|
1014
|
+
emitSwarmDelta(ctx, {
|
|
1015
|
+
type: synthesisResult.status === "failed" ? "swarm.phase.failed" : "swarm.phase.completed",
|
|
1016
|
+
phaseId: "synthesize",
|
|
1017
|
+
label: "Synthesize",
|
|
1018
|
+
status: synthesisResult.status,
|
|
1019
|
+
agent: synthesisResult.ranSynthesis
|
|
1020
|
+
? {
|
|
1021
|
+
taskId: cleanEventString(synthesis?.id || "synthesis"),
|
|
1022
|
+
nodeId: cleanEventString(synthesis?.id || "synthesis"),
|
|
1023
|
+
role: cleanEventString(synthesis?.label || "Synthesizer"),
|
|
1024
|
+
phaseId: "synthesize",
|
|
1025
|
+
status: synthesisResult.status,
|
|
1026
|
+
adapter: cleanEventString(synthesisResult.adapter || ""),
|
|
1027
|
+
agentHost: cleanEventString(synthesisResult.agentHost || ""),
|
|
1028
|
+
durationMs: Number(synthesisResult.durationMs) || 0,
|
|
1029
|
+
tokens: synthesisResult.tokens ?? null,
|
|
1030
|
+
tools: synthesisResult.tools ?? null,
|
|
1031
|
+
startedAt: cleanEventString(synthesisResult.startedAt || ""),
|
|
1032
|
+
endedAt: cleanEventString(synthesisResult.endedAt || ""),
|
|
1033
|
+
error: cleanEventString(synthesisResult.error || "")
|
|
1034
|
+
}
|
|
1035
|
+
: null
|
|
1036
|
+
});
|
|
817
1037
|
|
|
818
1038
|
const durationMs = Date.now() - startedAt;
|
|
819
1039
|
const requiredTasks = tasks.filter((t) => t.required);
|
|
@@ -863,6 +1083,15 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
863
1083
|
errorText = `synthesizer returned OUTCOME_SCORE ${semanticScore} (< 0.5)`;
|
|
864
1084
|
}
|
|
865
1085
|
|
|
1086
|
+
emitSwarmDelta(ctx, {
|
|
1087
|
+
type: outcomeOk ? "swarm.run.completed" : "swarm.run.failed",
|
|
1088
|
+
status: swarmStatus,
|
|
1089
|
+
durationMs,
|
|
1090
|
+
observedParallelism: observedParallelism || (subagents.length === 1 ? 1 : 0),
|
|
1091
|
+
reward,
|
|
1092
|
+
error: cleanEventString(errorText || "")
|
|
1093
|
+
});
|
|
1094
|
+
|
|
866
1095
|
return {
|
|
867
1096
|
ok: outcomeOk,
|
|
868
1097
|
exitCode: outcomeOk ? 0 : 1,
|
|
@@ -888,6 +1117,12 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
888
1117
|
adapter: orchestratorResult.adapter,
|
|
889
1118
|
agentHost: orchestratorResult.agentHost,
|
|
890
1119
|
durationMs: orchestratorResult.durationMs,
|
|
1120
|
+
adapterMeta: orchestratorResult.adapterMeta || null,
|
|
1121
|
+
tokens: orchestratorResult.tokens ?? null,
|
|
1122
|
+
tools: orchestratorResult.tools ?? null,
|
|
1123
|
+
startedAt: orchestratorResult.startedAt || "",
|
|
1124
|
+
endedAt: orchestratorResult.endedAt || "",
|
|
1125
|
+
phaseId: "plan",
|
|
891
1126
|
plan: clampText(orchestratorResult.plan, 4000)
|
|
892
1127
|
},
|
|
893
1128
|
tasks,
|
|
@@ -902,6 +1137,12 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
902
1137
|
adapter: synthesisResult.adapter,
|
|
903
1138
|
agentHost: synthesisResult.agentHost,
|
|
904
1139
|
durationMs: synthesisResult.durationMs,
|
|
1140
|
+
adapterMeta: synthesisResult.adapterMeta || null,
|
|
1141
|
+
tokens: synthesisResult.tokens ?? null,
|
|
1142
|
+
tools: synthesisResult.tools ?? null,
|
|
1143
|
+
startedAt: synthesisResult.startedAt || "",
|
|
1144
|
+
endedAt: synthesisResult.endedAt || "",
|
|
1145
|
+
phaseId: "synthesize",
|
|
905
1146
|
answer: clampText(synthesisResult.output, 4000),
|
|
906
1147
|
parsedOutcomeScore: synthesisResult.parsedOutcomeScore
|
|
907
1148
|
}
|
|
@@ -914,6 +1155,7 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
914
1155
|
export {
|
|
915
1156
|
runAgentSwarmGraphIfPresent,
|
|
916
1157
|
computeRewardTelemetry,
|
|
1158
|
+
extractAdapterTelemetry,
|
|
917
1159
|
buildOrchestratorCommand,
|
|
918
1160
|
buildSubtaskCommand,
|
|
919
1161
|
buildSynthesisCommand,
|
|
@@ -364,6 +364,67 @@ function buildSandboxRowFromApiRegistry(workspaceConfig, registryRow, options =
|
|
|
364
364
|
};
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Find existing data-source rows that already resolve through a given API
|
|
369
|
+
* Registry integration (by `registryId`). Mirrors findSandboxRowsForRegistry so
|
|
370
|
+
* the drawer can refuse to create a duplicate Data Source for the same API.
|
|
371
|
+
*/
|
|
372
|
+
function findDataSourceRowsForRegistry(workspaceConfig, integrationId) {
|
|
373
|
+
const id = String(integrationId || "").trim();
|
|
374
|
+
if (!id) return [];
|
|
375
|
+
const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
|
|
376
|
+
const rows = [];
|
|
377
|
+
for (const object of objects) {
|
|
378
|
+
if (object?.objectType !== "data-source") continue;
|
|
379
|
+
for (const row of Array.isArray(object.rows) ? object.rows : []) {
|
|
380
|
+
if (String(row?.registryId || "").trim() === id) rows.push(row);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return rows;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Build a governed Data Source row from a tested API Registry row. The Data
|
|
388
|
+
* Source references the registry entry by `registryId` (the existing
|
|
389
|
+
* resolver-binding relation) and keeps auth as an `authRef` slug only — the
|
|
390
|
+
* secret never lands on the row. Shape matches the OBJECT_TYPE_PRESETS
|
|
391
|
+
* "data-source" columns so it slots straight into the data-source table.
|
|
392
|
+
*/
|
|
393
|
+
function buildDataSourceRowFromApiRegistry(workspaceConfig, registryRow, options = {}) {
|
|
394
|
+
const integrationId = String(registryRow?.integrationId || "").trim();
|
|
395
|
+
const baseName = String(options.name || registryRow?.Name || integrationId || "Data Source").trim();
|
|
396
|
+
const name = baseName.endsWith(" Source") ? baseName : `${baseName} Source`;
|
|
397
|
+
const entityType = String(
|
|
398
|
+
options.entityType || registryRow?.entityTypes || "records"
|
|
399
|
+
).split(",")[0].trim() || "records";
|
|
400
|
+
const sourceId = String(
|
|
401
|
+
options.sourceId || slugifyName(`${integrationId || baseName}-${entityType}`) || slugifyName(baseName)
|
|
402
|
+
).trim();
|
|
403
|
+
const sourceStorage = String(options.sourceStorage || "workspace-source-records").trim();
|
|
404
|
+
return {
|
|
405
|
+
Name: name,
|
|
406
|
+
slug: options.slug || slugifyName(name) || slugifyName(integrationId),
|
|
407
|
+
objectType: "data-source",
|
|
408
|
+
registryId: integrationId,
|
|
409
|
+
endpoint: String(registryRow?.endpoint || "").trim(),
|
|
410
|
+
authRef: String(options.authRef || registryRow?.authRef || integrationId).trim(),
|
|
411
|
+
baseUrl: String(registryRow?.baseUrl || "").trim(),
|
|
412
|
+
method: String(registryRow?.method || "GET").trim().toUpperCase(),
|
|
413
|
+
status: "draft",
|
|
414
|
+
lastTested: "",
|
|
415
|
+
lastResponse: "",
|
|
416
|
+
entityType,
|
|
417
|
+
sourceId,
|
|
418
|
+
sourceStorage,
|
|
419
|
+
resolverTemplateId: String(options.resolverTemplateId || registryRow?.resolverTemplateId || "").trim(),
|
|
420
|
+
description: String(
|
|
421
|
+
options.description
|
|
422
|
+
|| registryRow?.description
|
|
423
|
+
|| `Data Source for ${integrationId || baseName} — resolves ${entityType} through the API Registry resolver. authRef ${String(options.authRef || registryRow?.authRef || integrationId).trim()} only; secrets resolve server-side.`
|
|
424
|
+
).trim()
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
367
428
|
function extractNodeByType(graph, type) {
|
|
368
429
|
const parsed = parseOrchestrationGraph(graph) || graph;
|
|
369
430
|
if (!parsed?.nodes) return null;
|
|
@@ -783,6 +844,12 @@ function buildDefaultAgentSwarmGraph(options = {}) {
|
|
|
783
844
|
role: String(agent.role || agent.id || "Subagent"),
|
|
784
845
|
description: String(agent.description || "").trim(),
|
|
785
846
|
taskPrompt: String(agent.taskPrompt || "").trim(),
|
|
847
|
+
// Optional author-named phase — groups this subagent in the cockpit
|
|
848
|
+
// (e.g. "ping" / "echo" / "verify"). Absent → the single Dispatch
|
|
849
|
+
// phase, exactly as before.
|
|
850
|
+
...(String(agent.phase || agent.phaseId || "").trim()
|
|
851
|
+
? { phase: String(agent.phase || agent.phaseId).trim().toLowerCase() }
|
|
852
|
+
: {}),
|
|
786
853
|
tools: Array.isArray(agent.tools) ? agent.tools.map((t) => String(t || "").trim()).filter(Boolean) : [],
|
|
787
854
|
agentHost: String(agent.agentHost || agentHost || "").trim(),
|
|
788
855
|
adapter: String(agent.adapter || "").trim(),
|
|
@@ -918,6 +985,8 @@ export {
|
|
|
918
985
|
getNextCanonicalNodeId,
|
|
919
986
|
addCanonicalNodeToGraph,
|
|
920
987
|
buildSandboxRowFromApiRegistry,
|
|
988
|
+
buildDataSourceRowFromApiRegistry,
|
|
989
|
+
findDataSourceRowsForRegistry,
|
|
921
990
|
extractApiRegistryCallNode,
|
|
922
991
|
extractInputNode,
|
|
923
992
|
extractTransformConfig,
|