Route workflow stages through task-scoped durable role agents. - Persist normalized multi-role agents and workflow principal fences with migrations. - Route planning, execution, review, and merge workflow nodes through authorized permanent principals with capacity leasing and recovery. - Retire ephemeral workflow-stage workers and expose role-aware agent configuration, workflow editing, and documentation. - Preserve lifecycle-column ratchet coverage by centralizing workflow-role classification rather than adding test exemptions. Files changed: .changeset/fn-8764-workflow-role-agents.md | 7 + CONCEPTS.md | 3 + docs/agents.md | 6 + docs/architecture.md | 6 + docs/cli-reference.md | 2 + docs/dashboard-guide.md | 4 + docs/settings-reference.md | 6 +- docs/storage.md | 2 + docs/workflow-steps.md | 6 + .../src/__tests__/extension-agent-update.test.ts | 11 +- packages/cli/src/__tests__/extension.test.ts | 18 +- packages/cli/src/extension.ts | 41 +- .../core/src/__tests__/agent-permissions.test.ts | 12 + .../core/src/__tests__/agent-role-policy.test.ts | 7 + packages/core/src/__tests__/agent-roles.test.ts | 21 + .../legacy-column-collection-gating-ledger.test.ts | 19 +- .../src/__tests__/postgres/schema-applier.test.ts | 16 +- .../core/src/__tests__/settings-parity.test.ts | 9 +- .../workflow-agent-node-classification.test.ts | 25 + .../src/__tests__/workflow-work-item-cas.test.ts | 38 ++ packages/core/src/agents/agent-permissions.ts | 11 +- packages/core/src/agents/agent-role-policy.ts | 39 +- packages/core/src/agents/agent-store.ts | 190 ++++++- .../core/src/async-stores/async-agent-store.ts | 6 + packages/core/src/config/settings-schema.ts | 5 +- packages/core/src/index.gate.ts | 2 +- packages/core/src/index.ts | 7 +- .../0045_fn_8764_multi_role_workflow_agents.sql | 20 + .../0046_fn_8764_workflow_principal_fence.sql | 49 ++ packages/core/src/postgres/schema-applier.ts | 22 +- packages/core/src/postgres/schema/project.ts | 21 + packages/core/src/store.ts | 2 +- .../task-store/async/async-workflow-workitems.ts | 49 +- packages/core/src/task-store/row-types.ts | 4 + packages/core/src/task-store/settings-helpers.ts | 16 +- packages/core/src/task-store/settings-ops-2.ts | 13 +- packages/core/src/task-store/settings-ops.ts | 16 +- packages/core/src/task-store/task-row-mappers.ts | 4 + .../src/task-store/workflow-task-create-ops.ts | 6 +- .../src/task-store/workflow-workitems-ops-2.ts | 25 +- packages/core/src/types.ts | 2 + packages/core/src/types/agents/agents.ts | 45 +- packages/core/src/types/merge/merge-queue.ts | 17 + packages/core/src/types/settings/settings-scope.ts | 9 +- packages/core/src/workflows/workflow-ir-types.ts | 58 +++ packages/core/src/workflows/workflow-ir.ts | 19 + .../dashboard/app/components/AgentDetailView.css | 14 + .../dashboard/app/components/AgentDetailView.tsx | 34 +- .../dashboard/app/components/NewAgentDialog.tsx | 28 +- .../app/components/WorkflowNodeEditor.tsx | 19 + .../__tests__/AgentDetailView.core.test.tsx | 4 +- .../app/components/__tests__/AgentsView.test.tsx | 2 +- .../__tests__/SettingsModal.general.test.tsx | 86 --- .../__tests__/SettingsModal.test-harness.tsx | 1 - .../components/agent-presets/agentCreatePayload.ts | 9 +- .../app/components/settings/section-keys.ts | 1 - .../settings/sections/GeneralSection.tsx | 8 - .../settings-default-descriptions.test.tsx | 1 - .../app/components/workflow-flow-mapping.ts | 7 + packages/dashboard/src/mission-routes.ts | 26 +- .../src/routes/__tests__/agent-core-routes.test.ts | 23 +- .../src/routes/register-agent-core-routes.ts | 42 +- ...gister-agent-import-export-generation-routes.ts | 21 - .../engine/src/__tests__/agent-action-gate.test.ts | 33 ++ .../engine/src/__tests__/agent-assignment.test.ts | 370 ------------- .../src/__tests__/ephemeral-worker-manager.test.ts | 575 --------------------- ...ecutor-ephemeral-disabled-dispatch-gate.test.ts | 223 -------- .../__tests__/executor-fast-mode-workflows.test.ts | 58 +++ .../engine/src/__tests__/log-severity-manifest.ts | 1 - .../__tests__/log-severity-spam-contract.test.ts | 3 - .../resolved-read-with-literal-filter.test.ts | 4 - .../__tests__/scheduler-ephemeral-toggle.test.ts | 175 ------- .../__tests__/scheduler-workflow-cutover.test.ts | 19 - .../src/__tests__/workflow-agent-capacity.test.ts | 47 ++ .../src/__tests__/workflow-agent-routing.test.ts | 137 +++++ .../src/__tests__/workflow-graph-foreach.test.ts | 15 + .../__tests__/workflow-graph-task-runner.test.ts | 73 +++ .../src/__tests__/workflow-task-runtime.test.ts | 95 ++++ .../src/__tests__/workflow-work-scheduler.test.ts | 20 + packages/engine/src/agents/agent-action-gate.ts | 64 +++ packages/engine/src/agents/agent-assignment.ts | 135 ----- packages/engine/src/agents/agent-reflection.ts | 1 + .../engine/src/agents/ephemeral-worker-manager.ts | 429 --------------- .../engine/src/agents/workflow-agent-capacity.ts | 113 ++++ .../engine/src/agents/workflow-agent-router.ts | 185 +++++++ packages/engine/src/execution/reviewer.ts | 26 +- packages/engine/src/executor.ts | 501 +++++++++++++++--- packages/engine/src/index.ts | 1 - packages/engine/src/merger.ts | 20 +- packages/engine/src/pi.ts | 11 + packages/engine/src/runtimes/in-process-runtime.ts | 37 -- packages/engine/src/scheduler.ts | 114 +--- packages/engine/src/triage.ts | 196 ++++++- .../src/workflows/workflow-graph-executor.ts | 109 +++- .../engine/src/workflows/workflow-graph-loop.ts | 13 +- .../src/workflows/workflow-graph-task-runner.ts | 12 + .../engine/src/workflows/workflow-task-runtime.ts | 125 ++++- .../src/workflows/workflow-work-scheduler.ts | 8 +- 98 files changed, 2722 insertions(+), 2468 deletions(-) Fusion-Task-Id: FN-8764 Fusion-Task-Lineage: 5527fccb-342d-46f6-8108-bbf89142efec Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
91 lines
3.7 KiB
TypeScript
91 lines
3.7 KiB
TypeScript
import type { AgentCapability, AgentCreateInput, AgentOnboardingSummary } from "../../api";
|
|
import type { AgentPreset } from "./index";
|
|
|
|
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
|
|
export const VALID_AGENT_CAPABILITIES = new Set<string>(["triage", "executor", "reviewer", "merger", "scheduler", "engineer", "custom"]);
|
|
|
|
export interface AgentDraftValues {
|
|
name: string;
|
|
role: AgentCapability;
|
|
/** Canonical multi-role tags; `role` remains the compatibility primary. */
|
|
roles?: AgentCapability[];
|
|
title?: string;
|
|
icon?: string;
|
|
reportsTo?: string;
|
|
instructionsPath?: string;
|
|
instructionsText?: string;
|
|
heartbeatProcedurePath?: string;
|
|
soul?: string;
|
|
memory?: string;
|
|
model?: string;
|
|
runtimeHint?: string;
|
|
thinkingLevel?: ThinkingLevel;
|
|
maxTurns?: number;
|
|
skills?: string[];
|
|
}
|
|
|
|
export function mapPresetToAgentDraft(preset: AgentPreset): AgentDraftValues {
|
|
return {
|
|
name: preset.name,
|
|
role: preset.role,
|
|
title: preset.description ?? preset.title,
|
|
icon: preset.icon,
|
|
soul: preset.soul ?? "",
|
|
instructionsText: preset.instructionsText ?? "",
|
|
};
|
|
}
|
|
|
|
export function mapOnboardingSummaryToAgentDraft(draft: AgentOnboardingSummary): AgentDraftValues {
|
|
const runtimeHint = draft.runtimeHint?.trim() ?? "";
|
|
const modelSelection = draft.model?.trim() || draft.modelHint?.trim() || "";
|
|
|
|
return {
|
|
name: draft.name ?? "",
|
|
title: draft.title ?? "",
|
|
icon: draft.icon ?? "",
|
|
role: (VALID_AGENT_CAPABILITIES.has(draft.role) ? draft.role : "custom") as AgentCapability,
|
|
reportsTo: draft.reportsTo ?? "",
|
|
instructionsText: draft.instructionsText ?? "",
|
|
heartbeatProcedurePath: draft.heartbeatProcedurePath ?? "",
|
|
soul: draft.soul ?? "",
|
|
memory: draft.memory ?? "",
|
|
skills: Array.isArray(draft.skills) ? draft.skills : [],
|
|
model: runtimeHint ? "" : modelSelection,
|
|
runtimeHint,
|
|
thinkingLevel: draft.thinkingLevel ?? undefined,
|
|
maxTurns: draft.maxTurns ?? undefined,
|
|
};
|
|
}
|
|
|
|
export function buildAgentCreatePayload(values: AgentDraftValues): AgentCreateInput {
|
|
const runtimeCfg: Record<string, unknown> = {};
|
|
if (values.runtimeHint?.trim()) {
|
|
runtimeCfg.runtimeHint = values.runtimeHint.trim();
|
|
} else if (values.model?.trim()) {
|
|
runtimeCfg.model = values.model.trim();
|
|
}
|
|
if (values.thinkingLevel && values.thinkingLevel !== "off") runtimeCfg.thinkingLevel = values.thinkingLevel;
|
|
if (values.maxTurns !== undefined && values.maxTurns !== 1000) runtimeCfg.maxTurns = values.maxTurns;
|
|
|
|
return {
|
|
name: values.name.trim(),
|
|
/*
|
|
* FNXC:WorkflowAgentRouting 2026-08-07-03:46:
|
|
* New permanent agents submit canonical role tags. The deprecated singular
|
|
* field remains an API compatibility seam, never the source of new data.
|
|
*/
|
|
roles: values.roles?.length ? values.roles : [values.role],
|
|
...(values.title?.trim() ? { title: values.title.trim() } : {}),
|
|
...(values.icon?.trim() ? { icon: values.icon.trim() } : {}),
|
|
...(values.reportsTo?.trim() ? { reportsTo: values.reportsTo.trim() } : {}),
|
|
...(values.instructionsPath?.trim() ? { instructionsPath: values.instructionsPath.trim() } : {}),
|
|
...(values.instructionsText?.trim() ? { instructionsText: values.instructionsText.trim() } : {}),
|
|
...(values.heartbeatProcedurePath?.trim() ? { heartbeatProcedurePath: values.heartbeatProcedurePath.trim() } : {}),
|
|
...(values.soul?.trim() ? { soul: values.soul.trim() } : {}),
|
|
...(values.memory?.trim() ? { memory: values.memory.trim() } : {}),
|
|
...(Object.keys(runtimeCfg).length > 0 ? { runtimeConfig: runtimeCfg } : {}),
|
|
...(values.skills && values.skills.length > 0 ? { metadata: { skills: values.skills } } : {}),
|
|
};
|
|
}
|