@growthub/cli 0.14.0 → 0.14.2
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/helper/apply/route.js +99 -2
- 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-agent-auth/login/route.js +3 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/logout/route.js +3 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/status/route.js +3 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +84 -10
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +107 -34
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +72 -15
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +264 -22
- 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 +179 -117
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxAgentAuthPanel.jsx +34 -14
- 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 +136 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +61 -13
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/sandbox-environment-primitive.md +26 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/adapters/local-intelligence-browser-access.js +516 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-agent-host.js +224 -11
- 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/adapters/sandboxes/default-local-process.js +3 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/index.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/sandbox-adapter-registry.js +5 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/field-contracts.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +254 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +3 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +10 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +412 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +82 -27
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +4 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +23 -0
- 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-schema.js +6 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-swarm-proposal.js +554 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package-lock.json +364 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -0
- package/package.json +1 -1
|
@@ -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();
|
|
@@ -235,12 +288,14 @@ async function runThroughAdapter({
|
|
|
235
288
|
timeoutMs,
|
|
236
289
|
networkAllow,
|
|
237
290
|
allowList,
|
|
291
|
+
browserAccess,
|
|
238
292
|
env,
|
|
239
293
|
envRefSlugs,
|
|
240
294
|
envRefsMissing,
|
|
241
295
|
runId,
|
|
242
296
|
name,
|
|
243
|
-
ranAt
|
|
297
|
+
ranAt,
|
|
298
|
+
intelligence
|
|
244
299
|
}) {
|
|
245
300
|
const adapter = getSandboxAdapter(adapterId);
|
|
246
301
|
if (!adapter) {
|
|
@@ -277,11 +332,25 @@ async function runThroughAdapter({
|
|
|
277
332
|
timeoutMs,
|
|
278
333
|
networkAllow,
|
|
279
334
|
allowList,
|
|
335
|
+
browserAccess: browserAccess === true,
|
|
280
336
|
env,
|
|
281
337
|
envRefSlugs,
|
|
282
338
|
envRefsMissing,
|
|
283
339
|
workdir,
|
|
284
|
-
ranAt: ranAt || new Date(startedAt).toISOString()
|
|
340
|
+
ranAt: ranAt || new Date(startedAt).toISOString(),
|
|
341
|
+
// local-intelligence speaks the intelligenceSandbox envelope — the
|
|
342
|
+
// phase prompt travels as userIntent; model/endpoint settings come
|
|
343
|
+
// from the governed row (slugs/URLs only, never secret values).
|
|
344
|
+
...(adapterId === "local-intelligence"
|
|
345
|
+
? {
|
|
346
|
+
intelligenceSandbox: {
|
|
347
|
+
userIntent: command,
|
|
348
|
+
localModel: String(intelligence?.localModel || "").trim(),
|
|
349
|
+
localEndpoint: String(intelligence?.localEndpoint || "").trim(),
|
|
350
|
+
intelligenceAdapterMode: String(intelligence?.intelligenceAdapterMode || "ollama").trim() || "ollama"
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
: {})
|
|
285
354
|
});
|
|
286
355
|
} catch (error) {
|
|
287
356
|
return {
|
|
@@ -324,6 +393,7 @@ async function runOrchestratorPhase({ orchestratorNode, subagents, inputPayload,
|
|
|
324
393
|
const command = buildOrchestratorCommand({ orchestratorNode, subagents, inputPayload });
|
|
325
394
|
const env = { ...(executionContext.env || {}), GROWTHUB_SWARM_PHASE: "orchestrator" };
|
|
326
395
|
const startedAt = Date.now();
|
|
396
|
+
const startedAtIso = new Date(startedAt).toISOString();
|
|
327
397
|
const result = await runThroughAdapter({
|
|
328
398
|
adapterId: resolved.adapterId,
|
|
329
399
|
agentHost: resolved.agentHost,
|
|
@@ -332,14 +402,17 @@ async function runOrchestratorPhase({ orchestratorNode, subagents, inputPayload,
|
|
|
332
402
|
timeoutMs: clampPositiveInt(orchestratorNode?.config?.timeoutMs, DEFAULT_ORCHESTRATOR_TIMEOUT_MS),
|
|
333
403
|
networkAllow: executionContext.networkAllow === true,
|
|
334
404
|
allowList: executionContext.allowList || [],
|
|
405
|
+
browserAccess: executionContext.browserAccess === true,
|
|
335
406
|
env,
|
|
336
407
|
envRefSlugs: executionContext.envRefSlugs || [],
|
|
337
408
|
envRefsMissing: executionContext.envRefsMissing || [],
|
|
409
|
+
intelligence: executionContext.intelligence,
|
|
338
410
|
runId: `${executionContext.runId}_orchestrator`,
|
|
339
411
|
name: `${executionContext.sandboxName || "swarm"}::orchestrator`
|
|
340
412
|
});
|
|
341
413
|
const stdout = redactSecretsFromText(result?.stdout || "");
|
|
342
414
|
const errorText = redactSecretsFromText(result?.error || "");
|
|
415
|
+
const telemetry = extractAdapterTelemetry(result);
|
|
343
416
|
return {
|
|
344
417
|
status: result?.ok === true && !errorText ? "completed" : "failed",
|
|
345
418
|
error: errorText,
|
|
@@ -349,6 +422,11 @@ async function runOrchestratorPhase({ orchestratorNode, subagents, inputPayload,
|
|
|
349
422
|
output: stdout,
|
|
350
423
|
stderr: redactSecretsFromText(result?.stderr || ""),
|
|
351
424
|
plan: stdout,
|
|
425
|
+
tokens: telemetry.tokens,
|
|
426
|
+
tools: telemetry.tools,
|
|
427
|
+
startedAt: startedAtIso,
|
|
428
|
+
endedAt: new Date().toISOString(),
|
|
429
|
+
phaseId: "plan",
|
|
352
430
|
adapterMeta: { ...(result?.adapterMeta || {}), swarmPhase: "orchestrator" }
|
|
353
431
|
};
|
|
354
432
|
}
|
|
@@ -364,6 +442,10 @@ async function dispatchSubagentTask({
|
|
|
364
442
|
const required = subagentConfig.required !== false;
|
|
365
443
|
const taskId = subagentNode?.id || `task-${taskIndex + 1}`;
|
|
366
444
|
const role = String(subagentConfig.role || subagentNode?.label || "subagent");
|
|
445
|
+
// Author-named phase (config.phase / config.phaseId) travels onto the task
|
|
446
|
+
// so the record projection groups under the same phases the declared
|
|
447
|
+
// skeleton shows. Absent → "dispatch", exactly as before.
|
|
448
|
+
const phaseId = String(subagentConfig.phase || subagentConfig.phaseId || "").trim().toLowerCase() || "dispatch";
|
|
367
449
|
|
|
368
450
|
const resolved = chooseAdapterIdForSubagent({
|
|
369
451
|
subagentConfig,
|
|
@@ -371,6 +453,7 @@ async function dispatchSubagentTask({
|
|
|
371
453
|
fallbackAgentHost: executionContext.agentHost
|
|
372
454
|
});
|
|
373
455
|
if (!resolved.adapterId || resolved.error) {
|
|
456
|
+
const gateAt = new Date().toISOString();
|
|
374
457
|
return {
|
|
375
458
|
taskId,
|
|
376
459
|
nodeId: taskId,
|
|
@@ -383,6 +466,11 @@ async function dispatchSubagentTask({
|
|
|
383
466
|
stdout: "",
|
|
384
467
|
stderr: "",
|
|
385
468
|
error: resolved.error || "no prompt-capable adapter resolved for subagent",
|
|
469
|
+
tokens: null,
|
|
470
|
+
tools: null,
|
|
471
|
+
startedAt: gateAt,
|
|
472
|
+
endedAt: gateAt,
|
|
473
|
+
phaseId,
|
|
386
474
|
adapterMeta: { swarmSubagent: true, reason: "adapter-gate" }
|
|
387
475
|
};
|
|
388
476
|
}
|
|
@@ -403,15 +491,20 @@ async function dispatchSubagentTask({
|
|
|
403
491
|
command,
|
|
404
492
|
timeoutMs: clampPositiveInt(subagentConfig.timeoutMs, executionContext.timeoutMs || DEFAULT_SUBAGENT_TIMEOUT_MS),
|
|
405
493
|
networkAllow: subagentConfig.networkAccess === true && executionContext.networkAllow === true,
|
|
494
|
+
// Browser is a superset of network: a subagent only inherits the row's
|
|
495
|
+
// browser access through the same node-level network gate.
|
|
496
|
+
browserAccess: subagentConfig.networkAccess === true && executionContext.browserAccess === true,
|
|
406
497
|
allowList: executionContext.allowList || [],
|
|
407
498
|
env,
|
|
408
499
|
envRefSlugs: executionContext.envRefSlugs || [],
|
|
409
500
|
envRefsMissing: executionContext.envRefsMissing || [],
|
|
501
|
+
intelligence: executionContext.intelligence,
|
|
410
502
|
runId: `${executionContext.runId}_${taskId}`,
|
|
411
503
|
name: `${executionContext.sandboxName || "swarm"}::${taskId}`
|
|
412
504
|
});
|
|
413
505
|
const errorText = redactSecretsFromText(result?.error || "");
|
|
414
506
|
const ok = result?.ok === true && !errorText;
|
|
507
|
+
const telemetry = extractAdapterTelemetry(result);
|
|
415
508
|
return {
|
|
416
509
|
taskId,
|
|
417
510
|
nodeId: taskId,
|
|
@@ -425,6 +518,11 @@ async function dispatchSubagentTask({
|
|
|
425
518
|
stdout: redactSecretsFromText(result?.stdout || ""),
|
|
426
519
|
stderr: redactSecretsFromText(result?.stderr || ""),
|
|
427
520
|
error: errorText,
|
|
521
|
+
tokens: telemetry.tokens,
|
|
522
|
+
tools: telemetry.tools,
|
|
523
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
524
|
+
endedAt: new Date().toISOString(),
|
|
525
|
+
phaseId,
|
|
428
526
|
adapterMeta: { ...(result?.adapterMeta || {}), swarmSubagent: true }
|
|
429
527
|
};
|
|
430
528
|
}
|
|
@@ -467,6 +565,7 @@ async function runSynthesisPhase({ synthesisNode, swarmConfig, tasks, inputPaylo
|
|
|
467
565
|
const command = buildSynthesisCommand({ synthesisNode, swarmConfig, tasks, inputPayload });
|
|
468
566
|
const env = { ...(executionContext.env || {}), GROWTHUB_SWARM_PHASE: "synthesis" };
|
|
469
567
|
const startedAt = Date.now();
|
|
568
|
+
const startedAtIso = new Date(startedAt).toISOString();
|
|
470
569
|
const result = await runThroughAdapter({
|
|
471
570
|
adapterId: resolved.adapterId,
|
|
472
571
|
agentHost: resolved.agentHost,
|
|
@@ -475,9 +574,11 @@ async function runSynthesisPhase({ synthesisNode, swarmConfig, tasks, inputPaylo
|
|
|
475
574
|
timeoutMs: clampPositiveInt(cfg.timeoutMs, DEFAULT_SYNTHESIS_TIMEOUT_MS),
|
|
476
575
|
networkAllow: executionContext.networkAllow === true,
|
|
477
576
|
allowList: executionContext.allowList || [],
|
|
577
|
+
browserAccess: executionContext.browserAccess === true,
|
|
478
578
|
env,
|
|
479
579
|
envRefSlugs: executionContext.envRefSlugs || [],
|
|
480
580
|
envRefsMissing: executionContext.envRefsMissing || [],
|
|
581
|
+
intelligence: executionContext.intelligence,
|
|
481
582
|
runId: `${executionContext.runId}_synthesis`,
|
|
482
583
|
name: `${executionContext.sandboxName || "swarm"}::synthesis`
|
|
483
584
|
});
|
|
@@ -485,6 +586,7 @@ async function runSynthesisPhase({ synthesisNode, swarmConfig, tasks, inputPaylo
|
|
|
485
586
|
const errorText = redactSecretsFromText(result?.error || "");
|
|
486
587
|
const match = stdout.match(OUTCOME_SCORE_RE);
|
|
487
588
|
const parsedOutcomeScore = match ? clamp01(match[1]) : null;
|
|
589
|
+
const telemetry = extractAdapterTelemetry(result);
|
|
488
590
|
return {
|
|
489
591
|
status: result?.ok === true && !errorText ? "completed" : "failed",
|
|
490
592
|
ranSynthesis: true,
|
|
@@ -495,6 +597,11 @@ async function runSynthesisPhase({ synthesisNode, swarmConfig, tasks, inputPaylo
|
|
|
495
597
|
adapter: resolved.adapterId,
|
|
496
598
|
agentHost: resolved.agentHost || "",
|
|
497
599
|
parsedOutcomeScore,
|
|
600
|
+
tokens: telemetry.tokens,
|
|
601
|
+
tools: telemetry.tools,
|
|
602
|
+
startedAt: startedAtIso,
|
|
603
|
+
endedAt: new Date().toISOString(),
|
|
604
|
+
phaseId: "synthesize",
|
|
498
605
|
adapterMeta: { ...(result?.adapterMeta || {}), swarmPhase: "synthesis" }
|
|
499
606
|
};
|
|
500
607
|
}
|
|
@@ -722,19 +829,65 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
722
829
|
envRefsMissing: executionContext?.envRefsMissing || [],
|
|
723
830
|
networkAllow: executionContext?.networkAllow === true,
|
|
724
831
|
allowList: executionContext?.allowList || [],
|
|
832
|
+
browserAccess: executionContext?.browserAccess === true,
|
|
725
833
|
timeoutMs: clampPositiveInt(timeoutMs, DEFAULT_SUBAGENT_TIMEOUT_MS),
|
|
726
|
-
sandboxName: executionContext?.sandboxName || row?.Name || "swarm"
|
|
834
|
+
sandboxName: executionContext?.sandboxName || row?.Name || "swarm",
|
|
835
|
+
onEvent: executionContext?.onEvent,
|
|
836
|
+
// Row-configured local-intelligence settings (model id, endpoint URL,
|
|
837
|
+
// adapter mode) — configuration only, never secret values.
|
|
838
|
+
intelligence: {
|
|
839
|
+
localModel: String(row?.localModel || "").trim(),
|
|
840
|
+
localEndpoint: String(row?.localEndpoint || "").trim(),
|
|
841
|
+
intelligenceAdapterMode: String(row?.intelligenceAdapterMode || "").trim() || "ollama"
|
|
842
|
+
}
|
|
727
843
|
};
|
|
728
844
|
|
|
729
845
|
const startedAt = Date.now();
|
|
846
|
+
emitSwarmDelta(ctx, {
|
|
847
|
+
type: "swarm.run.started",
|
|
848
|
+
status: "running",
|
|
849
|
+
name: cleanEventString(ctx.sandboxName),
|
|
850
|
+
adapter: cleanEventString(ctx.adapterId),
|
|
851
|
+
agentHost: cleanEventString(ctx.agentHost),
|
|
852
|
+
maxConcurrency,
|
|
853
|
+
phaseCount: 3,
|
|
854
|
+
taskCount: subagents.length
|
|
855
|
+
});
|
|
730
856
|
|
|
731
857
|
// Phase 1: Plan ----------------------------------------------------------
|
|
858
|
+
emitSwarmDelta(ctx, {
|
|
859
|
+
type: "swarm.phase.started",
|
|
860
|
+
phaseId: "plan",
|
|
861
|
+
label: "Plan",
|
|
862
|
+
status: "running"
|
|
863
|
+
});
|
|
732
864
|
const orchestratorResult = await runOrchestratorPhase({
|
|
733
865
|
orchestratorNode: orchestrator,
|
|
734
866
|
subagents,
|
|
735
867
|
inputPayload: manualPayload,
|
|
736
868
|
executionContext: ctx
|
|
737
869
|
});
|
|
870
|
+
emitSwarmDelta(ctx, {
|
|
871
|
+
type: orchestratorResult.status === "failed" ? "swarm.phase.failed" : "swarm.phase.completed",
|
|
872
|
+
phaseId: "plan",
|
|
873
|
+
label: "Plan",
|
|
874
|
+
status: orchestratorResult.status,
|
|
875
|
+
agent: {
|
|
876
|
+
taskId: cleanEventString(orchestrator.id || "orchestrator"),
|
|
877
|
+
nodeId: cleanEventString(orchestrator.id || "orchestrator"),
|
|
878
|
+
role: cleanEventString(orchestrator?.config?.role || "Orchestrator"),
|
|
879
|
+
phaseId: "plan",
|
|
880
|
+
status: orchestratorResult.status,
|
|
881
|
+
adapter: cleanEventString(orchestratorResult.adapter || ""),
|
|
882
|
+
agentHost: cleanEventString(orchestratorResult.agentHost || ""),
|
|
883
|
+
durationMs: Number(orchestratorResult.durationMs) || 0,
|
|
884
|
+
tokens: orchestratorResult.tokens ?? null,
|
|
885
|
+
tools: orchestratorResult.tools ?? null,
|
|
886
|
+
startedAt: cleanEventString(orchestratorResult.startedAt || ""),
|
|
887
|
+
endedAt: cleanEventString(orchestratorResult.endedAt || ""),
|
|
888
|
+
error: cleanEventString(orchestratorResult.error || "")
|
|
889
|
+
}
|
|
890
|
+
});
|
|
738
891
|
|
|
739
892
|
if (orchestratorResult.status === "failed" && String(orchestratorResult.error || "").length > 0) {
|
|
740
893
|
const durationMs = Date.now() - startedAt;
|
|
@@ -747,6 +900,13 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
747
900
|
outcomeOk: false,
|
|
748
901
|
synthesisResult: null
|
|
749
902
|
});
|
|
903
|
+
emitSwarmDelta(ctx, {
|
|
904
|
+
type: "swarm.run.failed",
|
|
905
|
+
status: "failed",
|
|
906
|
+
phaseFailed: "plan",
|
|
907
|
+
durationMs,
|
|
908
|
+
error: cleanEventString(orchestratorResult.error || "")
|
|
909
|
+
});
|
|
750
910
|
return {
|
|
751
911
|
ok: false,
|
|
752
912
|
exitCode: 1,
|
|
@@ -790,23 +950,68 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
790
950
|
// Phase 2: Dispatch ------------------------------------------------------
|
|
791
951
|
let observedParallelism = 0;
|
|
792
952
|
let activeNow = 0;
|
|
953
|
+
emitSwarmDelta(ctx, {
|
|
954
|
+
type: "swarm.phase.started",
|
|
955
|
+
phaseId: "dispatch",
|
|
956
|
+
label: "Dispatch",
|
|
957
|
+
status: "running",
|
|
958
|
+
taskCount: subagents.length
|
|
959
|
+
});
|
|
793
960
|
const tasks = await runSubagentsWithConcurrency(subagents, maxConcurrency, async (subagentNode, index) => {
|
|
794
961
|
activeNow += 1;
|
|
795
962
|
if (activeNow > observedParallelism) observedParallelism = activeNow;
|
|
963
|
+
const subagentConfig = subagentNode?.config || {};
|
|
964
|
+
const taskId = subagentNode?.id || `task-${index + 1}`;
|
|
965
|
+
const phaseId = String(subagentConfig.phase || subagentConfig.phaseId || "").trim().toLowerCase() || "dispatch";
|
|
966
|
+
emitSwarmDelta(ctx, {
|
|
967
|
+
type: "swarm.task.started",
|
|
968
|
+
phaseId,
|
|
969
|
+
taskId: cleanEventString(taskId),
|
|
970
|
+
nodeId: cleanEventString(taskId),
|
|
971
|
+
role: cleanEventString(subagentConfig.role || subagentNode?.label || `Agent ${index + 1}`),
|
|
972
|
+
status: "running",
|
|
973
|
+
active: activeNow,
|
|
974
|
+
observedParallelism
|
|
975
|
+
});
|
|
796
976
|
try {
|
|
797
|
-
|
|
977
|
+
const task = await dispatchSubagentTask({
|
|
798
978
|
subagentNode,
|
|
799
979
|
orchestratorPlan: orchestratorResult.plan,
|
|
800
980
|
inputPayload: manualPayload,
|
|
801
981
|
executionContext: ctx,
|
|
802
982
|
taskIndex: index
|
|
803
983
|
});
|
|
984
|
+
emitSwarmDelta(ctx, {
|
|
985
|
+
type: task.status === "completed" ? "swarm.task.completed" : "swarm.task.failed",
|
|
986
|
+
phaseId: task.phaseId || phaseId,
|
|
987
|
+
task: taskDeltaFromResult(task)
|
|
988
|
+
});
|
|
989
|
+
return task;
|
|
804
990
|
} finally {
|
|
805
991
|
activeNow -= 1;
|
|
806
992
|
}
|
|
807
993
|
});
|
|
994
|
+
const dispatchStatus = tasks.length > 0 && tasks.every((t) => t.status === "completed")
|
|
995
|
+
? "completed"
|
|
996
|
+
: tasks.some((t) => t.status === "failed")
|
|
997
|
+
? "failed"
|
|
998
|
+
: "completed";
|
|
999
|
+
emitSwarmDelta(ctx, {
|
|
1000
|
+
type: dispatchStatus === "failed" ? "swarm.phase.failed" : "swarm.phase.completed",
|
|
1001
|
+
phaseId: "dispatch",
|
|
1002
|
+
label: "Dispatch",
|
|
1003
|
+
status: dispatchStatus,
|
|
1004
|
+
taskCount: tasks.length,
|
|
1005
|
+
observedParallelism
|
|
1006
|
+
});
|
|
808
1007
|
|
|
809
1008
|
// Phase 3: Synthesize ----------------------------------------------------
|
|
1009
|
+
emitSwarmDelta(ctx, {
|
|
1010
|
+
type: "swarm.phase.started",
|
|
1011
|
+
phaseId: "synthesize",
|
|
1012
|
+
label: "Synthesize",
|
|
1013
|
+
status: "running"
|
|
1014
|
+
});
|
|
810
1015
|
const synthesisResult = await runSynthesisPhase({
|
|
811
1016
|
synthesisNode: synthesis,
|
|
812
1017
|
swarmConfig,
|
|
@@ -814,6 +1019,29 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
814
1019
|
inputPayload: manualPayload,
|
|
815
1020
|
executionContext: ctx
|
|
816
1021
|
});
|
|
1022
|
+
emitSwarmDelta(ctx, {
|
|
1023
|
+
type: synthesisResult.status === "failed" ? "swarm.phase.failed" : "swarm.phase.completed",
|
|
1024
|
+
phaseId: "synthesize",
|
|
1025
|
+
label: "Synthesize",
|
|
1026
|
+
status: synthesisResult.status,
|
|
1027
|
+
agent: synthesisResult.ranSynthesis
|
|
1028
|
+
? {
|
|
1029
|
+
taskId: cleanEventString(synthesis?.id || "synthesis"),
|
|
1030
|
+
nodeId: cleanEventString(synthesis?.id || "synthesis"),
|
|
1031
|
+
role: cleanEventString(synthesis?.label || "Synthesizer"),
|
|
1032
|
+
phaseId: "synthesize",
|
|
1033
|
+
status: synthesisResult.status,
|
|
1034
|
+
adapter: cleanEventString(synthesisResult.adapter || ""),
|
|
1035
|
+
agentHost: cleanEventString(synthesisResult.agentHost || ""),
|
|
1036
|
+
durationMs: Number(synthesisResult.durationMs) || 0,
|
|
1037
|
+
tokens: synthesisResult.tokens ?? null,
|
|
1038
|
+
tools: synthesisResult.tools ?? null,
|
|
1039
|
+
startedAt: cleanEventString(synthesisResult.startedAt || ""),
|
|
1040
|
+
endedAt: cleanEventString(synthesisResult.endedAt || ""),
|
|
1041
|
+
error: cleanEventString(synthesisResult.error || "")
|
|
1042
|
+
}
|
|
1043
|
+
: null
|
|
1044
|
+
});
|
|
817
1045
|
|
|
818
1046
|
const durationMs = Date.now() - startedAt;
|
|
819
1047
|
const requiredTasks = tasks.filter((t) => t.required);
|
|
@@ -863,6 +1091,15 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
863
1091
|
errorText = `synthesizer returned OUTCOME_SCORE ${semanticScore} (< 0.5)`;
|
|
864
1092
|
}
|
|
865
1093
|
|
|
1094
|
+
emitSwarmDelta(ctx, {
|
|
1095
|
+
type: outcomeOk ? "swarm.run.completed" : "swarm.run.failed",
|
|
1096
|
+
status: swarmStatus,
|
|
1097
|
+
durationMs,
|
|
1098
|
+
observedParallelism: observedParallelism || (subagents.length === 1 ? 1 : 0),
|
|
1099
|
+
reward,
|
|
1100
|
+
error: cleanEventString(errorText || "")
|
|
1101
|
+
});
|
|
1102
|
+
|
|
866
1103
|
return {
|
|
867
1104
|
ok: outcomeOk,
|
|
868
1105
|
exitCode: outcomeOk ? 0 : 1,
|
|
@@ -888,6 +1125,12 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
888
1125
|
adapter: orchestratorResult.adapter,
|
|
889
1126
|
agentHost: orchestratorResult.agentHost,
|
|
890
1127
|
durationMs: orchestratorResult.durationMs,
|
|
1128
|
+
adapterMeta: orchestratorResult.adapterMeta || null,
|
|
1129
|
+
tokens: orchestratorResult.tokens ?? null,
|
|
1130
|
+
tools: orchestratorResult.tools ?? null,
|
|
1131
|
+
startedAt: orchestratorResult.startedAt || "",
|
|
1132
|
+
endedAt: orchestratorResult.endedAt || "",
|
|
1133
|
+
phaseId: "plan",
|
|
891
1134
|
plan: clampText(orchestratorResult.plan, 4000)
|
|
892
1135
|
},
|
|
893
1136
|
tasks,
|
|
@@ -902,6 +1145,12 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
902
1145
|
adapter: synthesisResult.adapter,
|
|
903
1146
|
agentHost: synthesisResult.agentHost,
|
|
904
1147
|
durationMs: synthesisResult.durationMs,
|
|
1148
|
+
adapterMeta: synthesisResult.adapterMeta || null,
|
|
1149
|
+
tokens: synthesisResult.tokens ?? null,
|
|
1150
|
+
tools: synthesisResult.tools ?? null,
|
|
1151
|
+
startedAt: synthesisResult.startedAt || "",
|
|
1152
|
+
endedAt: synthesisResult.endedAt || "",
|
|
1153
|
+
phaseId: "synthesize",
|
|
905
1154
|
answer: clampText(synthesisResult.output, 4000),
|
|
906
1155
|
parsedOutcomeScore: synthesisResult.parsedOutcomeScore
|
|
907
1156
|
}
|
|
@@ -914,6 +1163,7 @@ async function runAgentSwarmGraphIfPresent({
|
|
|
914
1163
|
export {
|
|
915
1164
|
runAgentSwarmGraphIfPresent,
|
|
916
1165
|
computeRewardTelemetry,
|
|
1166
|
+
extractAdapterTelemetry,
|
|
917
1167
|
buildOrchestratorCommand,
|
|
918
1168
|
buildSubtaskCommand,
|
|
919
1169
|
buildSynthesisCommand,
|
|
@@ -281,6 +281,9 @@ async function runOrchestrationGraphIfPresent({ workspaceConfig, row, timeoutMs,
|
|
|
281
281
|
|
|
282
282
|
const apiNode = extractApiRegistryCallNode(graph);
|
|
283
283
|
if (!apiNode?.config) {
|
|
284
|
+
if ((Array.isArray(graph.nodes) ? graph.nodes : []).some((node) => node?.type === "ai-agent")) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
284
287
|
return {
|
|
285
288
|
ok: false,
|
|
286
289
|
exitCode: 1,
|
|
@@ -157,9 +157,10 @@ function validateOrchestrationGraph(graph) {
|
|
|
157
157
|
} else {
|
|
158
158
|
const hasThinAdapter = graph.nodes.some((n) => n?.type === "thinAdapter");
|
|
159
159
|
const hasApi = graph.nodes.some((n) => n?.type === "api-registry-call");
|
|
160
|
+
const hasAiAgent = graph.nodes.some((n) => n?.type === "ai-agent");
|
|
160
161
|
const hasResult = graph.nodes.some((n) => n?.type === "tool-result");
|
|
161
|
-
if (!hasThinAdapter && !hasApi) errors.push("orchestrationGraph requires an
|
|
162
|
-
if (!hasThinAdapter && !hasResult) errors.push("orchestrationGraph requires a tool-result node");
|
|
162
|
+
if (!hasThinAdapter && !hasApi && !hasAiAgent) errors.push("orchestrationGraph requires an executable node");
|
|
163
|
+
if (!hasThinAdapter && !hasAiAgent && !hasResult) errors.push("orchestrationGraph requires a tool-result node");
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
if (!Array.isArray(graph.edges)) {
|
|
@@ -712,6 +713,7 @@ function buildCanonicalNode(nodeId, registryRow = {}, options = {}) {
|
|
|
712
713
|
function getNextCanonicalNodeId(graph) {
|
|
713
714
|
const parsed = parseOrchestrationGraph(graph) || graph;
|
|
714
715
|
if ((parsed?.nodes || []).some((n) => n?.type === "thinAdapter")) return null;
|
|
716
|
+
if ((parsed?.nodes || []).some((n) => n?.type === "ai-agent")) return null;
|
|
715
717
|
const ids = new Set((parsed?.nodes || []).map((n) => String(n.id)));
|
|
716
718
|
for (const id of CANONICAL_NODE_ORDER) {
|
|
717
719
|
if (!ids.has(id)) return id;
|
|
@@ -844,6 +846,12 @@ function buildDefaultAgentSwarmGraph(options = {}) {
|
|
|
844
846
|
role: String(agent.role || agent.id || "Subagent"),
|
|
845
847
|
description: String(agent.description || "").trim(),
|
|
846
848
|
taskPrompt: String(agent.taskPrompt || "").trim(),
|
|
849
|
+
// Optional author-named phase — groups this subagent in the cockpit
|
|
850
|
+
// (e.g. "ping" / "echo" / "verify"). Absent → the single Dispatch
|
|
851
|
+
// phase, exactly as before.
|
|
852
|
+
...(String(agent.phase || agent.phaseId || "").trim()
|
|
853
|
+
? { phase: String(agent.phase || agent.phaseId).trim().toLowerCase() }
|
|
854
|
+
: {}),
|
|
847
855
|
tools: Array.isArray(agent.tools) ? agent.tools.map((t) => String(t || "").trim()).filter(Boolean) : [],
|
|
848
856
|
agentHost: String(agent.agentHost || agentHost || "").trim(),
|
|
849
857
|
adapter: String(agent.adapter || "").trim(),
|