Member Junction
    Preparing search index...

    AgentRunner provides a thin wrapper for executing AI agents.

    This class handles:

    • Loading agent type metadata to get the DriverClass
    • Instantiating the correct agent class using ClassFactory
    • Passing through to the agent's Execute method

    AgentRunner

    const runner = new AgentRunner();
    const result = await runner.RunAgent({
    agent: myAgent,
    conversationMessages: messages,
    contextUser: currentUser
    });
    Index

    Constructors

    Methods

    • Checks whether the serialized content for a new artifact version is identical to the latest existing version of the same artifact, using SHA-256 content hashing.

      When the content is unchanged, creating a new version adds noise without value. This method computes the hash of the candidate content and compares it against the ContentHash stored on the most recent version (populated by MJArtifactVersionEntityServer.Save()).

      Parameters

      • artifactId: string

        The artifact whose latest version to compare against

      • candidateContent: string

        The serialized (JSON-stringified) content that would become the new version

      • latestVersionNumber: number

        The version number of the current latest version

      • contextUser: UserInfo

        User context for the RunView query

      • Optionalprovider: IMetadataProvider

      Returns Promise<string>

      The existing version's ID if content is identical, or null if a new version should be created

    • Creates MJ: Artifact + MJ: Artifact Version + MJ: Conversation Detail Artifact junction records for agent-produced media outputs (images, audio, video).

      Replaces the deprecated CreateConversationMediaAttachments path which wrote to the MJ: Conversation Detail Attachments table (then auto-paired to an artifact via the server-side hook). Writing artifacts directly removes the deprecated-entity dependency and the redundant dual-write.

      Reuses createArtifactWithVersion — the same helper that ProcessFileArtifacts uses — so artifact creation logic (MIME resolution, transaction wrapping, junction linking) lives in exactly one place.

      Parameters

      • conversationDetailId: string

        The conversation detail to link artifacts to

      • mediaOutputs: MediaOutput[]

        Media outputs to persist as artifacts

      • contextUser: UserInfo

        User context for DB operations

      • Optionalprovider: IMetadataProvider

        Optional metadata provider for multi-provider support

      • OptionalresolvedStorageAccountId: string

        Pre-resolved storage account from the agent's resolution chain. Optional, and optional on purpose: this is a public method, and callers that predate storage-backed media keep working — omitting it just falls back to the first active account inside uploadBase64ToStorage.

      Returns Promise<void>

      5.38.0

    • Finds the most recent artifact for a conversation detail to determine versioning. Queries the junction table to locate artifacts linked to a specific conversation message.

      Parameters

      • conversationDetailId: string

        The conversation detail ID to query

      • contextUser: UserInfo

        The user context for the query

      • Optionalprovider: IMetadataProvider

      Returns Promise<{ artifactId: string; versionNumber: number }>

      Artifact info if exists, null if this is the first artifact for this message

      const runner = new AgentRunner();
      const previousArtifact = await runner.FindPreviousArtifactForMessage(detailId, currentUser);
      if (previousArtifact) {
      console.log(`Found artifact ${previousArtifact.artifactId} at version ${previousArtifact.versionNumber}`);
      }
    • Gets the maximum version number for an artifact. Used when creating new versions of explicitly specified artifacts.

      Parameters

      • artifactId: string

        The artifact ID to query

      • contextUser: UserInfo

        The user context for the query

      • Optionalprovider: IMetadataProvider

      Returns Promise<number>

      The maximum version number, or 0 if no versions exist

      const runner = new AgentRunner();
      const maxVersion = await runner.GetMaxVersionForArtifact(artifactId, currentUser);
      const newVersionNumber = maxVersion + 1;
    • Creates a ConversationDetailArtifact junction record linking an artifact version to a conversation detail, then returns the standard artifact result tuple.

      Extracted as a helper so both the normal version-creation path and the duplicate-skip path can share the same linking and return logic.

      Parameters

      • versionId: string

        The artifact version ID to link

      • conversationDetailId: string

        The conversation detail to link to

      • artifactId: string

        The parent artifact ID (passed through to the return value)

      • versionNumber: number

        The version number (passed through to the return value)

      • contextUser: UserInfo

        User context for the save operation

      • provider: IMetadataProvider

        Metadata provider for entity creation

      Returns Promise<{ artifactId: string; versionId: string; versionNumber: number }>

      The standard artifact result tuple

    • Processes agent completion to create artifacts from the agent's payload. Handles artifact creation, versioning, and linking to conversation details.

      This method implements intelligent artifact versioning: 0. If the agent supplied an artifactDirective, it decides: 'suppress' → nothing; 'create-new' → new artifact (sourceArtifactId ignored); 'version-source' → version targetArtifactId, else sourceArtifactId. A directive-named target is model output and is vetted first (UUID shape, existence, and the caller's right to write to it — see VetArtifactVersionTarget); anything failing falls back down the ladder to the caller's sourceArtifactId and then to the historical chain, so a directive can never widen what the caller was already allowed to do.

      1. Otherwise, if sourceArtifactId is provided (explicit continuity), creates new version of that artifact
      2. Otherwise, checks for previous artifacts on this conversation detail
      3. If previous artifact exists, creates new version of it
      4. If no previous artifact, creates entirely new artifact

      Respects the agent's ArtifactCreationMode configuration:

      • "Never": Skips artifact creation entirely
      • "System Only": Creates artifact with Visibility='System Only'
      • Other modes: Creates artifact with Visibility='Always'

      Type Parameters

      • R

      Parameters

      • agentResult: ExecuteAgentResult<R>

        The result from agent execution containing the payload

      • conversationDetailId: string

        The conversation detail to link the artifact to. May be undefined for runs executed OUTSIDE a conversation context (e.g. realtime voice delegations): the artifact + version are still created, but the previous-artifact lookup and the ConversationDetailArtifact junction link are skipped.

      • sourceArtifactId: string

        Optional explicit artifact to version from (agent continuity)

      • contextUser: UserInfo

        The user context for the operation

      • Optionalprovider: IMetadataProvider

      Returns Promise<{ artifactId: string; versionId: string; versionNumber: number }>

      Artifact metadata if created, undefined if skipped or failed

      const runner = new AgentRunner();
      const artifactInfo = await runner.ProcessAgentArtifacts(
      agentResult,
      conversationDetailId,
      sourceArtifactId, // Optional
      currentUser
      );
      if (artifactInfo) {
      console.log(`Created artifact ${artifactInfo.artifactId} version ${artifactInfo.versionNumber}`);
      }
    • Creates MJ: Artifact records for file outputs collected during agent execution. Reads directly from ExecuteAgentResult.fileOutputs — no DB query needed.

      Called automatically by RunAgentInConversation after the agent completes.

      Parameters

      • fileOutputs: FileOutputRef[]

        File outputs collected by BaseAgent during action execution

      • conversationDetailId: string

        The conversation detail to link artifacts to

      • contextUser: UserInfo

        User context for DB operations

      • OptionalresolvedStorageAccountId: string

        Pre-resolved FileStorageAccount ID from the agent's hierarchical resolution chain (Runtime → Agent → Category → Type → fallback). When provided, uploads use this specific account instead of picking the first active one.

      • Optionalprovider: IMetadataProvider
      • OptionalacceptUnregisteredFiles: boolean

      Returns Promise<CreatedArtifactInfo[]>

    • Runs an AI agent with the specified parameters.

      This method acts as a thin pass-through that:

      1. Loads the agent type to get the DriverClass
      2. Uses ClassFactory to instantiate the correct agent class
      3. Calls Execute on the agent instance and returns the result

      Type Parameters

      • C = any

        The type of the agent's context as provided in the ExecuteAgentParams

      • R = any

        The type of the agent's result as returned in ExecuteAgentResult

      Parameters

      Returns Promise<ExecuteAgentResult<R>>

      The execution result (same as BaseAgent.Execute)

      Throws if agent type loading fails or agent instantiation fails

    • Runs an AI agent within a conversation context, handling conversation and artifact management.

      This method provides a complete workflow for running agents in conversations:

      1. Creates or uses existing conversation
      2. Creates conversation detail record for the user message
      3. Executes the agent
      4. Creates artifacts from the agent's payload (if configured)
      5. Links artifacts to the conversation detail

      Type Parameters

      • C = any
      • R = any

      Parameters

      • params: ExecuteAgentParams<C>

        Core agent execution parameters

      • options: {
            conversationDetailId?: string;
            conversationId?: string;
            conversationName?: string;
            createArtifacts?: boolean;
            sourceArtifactId?: string;
            testRunId?: string;
            userMessage?: string;
        }

        Conversation-specific options

        • OptionalconversationDetailId?: string

          Optional existing conversation detail ID. If provided, skips conversation/detail creation

        • OptionalconversationId?: string

          Optional existing conversation ID. If not provided, a new conversation will be created

        • OptionalconversationName?: string

          Optional conversation name (only used when creating new conversation)

        • OptionalcreateArtifacts?: boolean

          Whether to create artifacts from the agent's payload (default: true)

        • OptionalsourceArtifactId?: string

          Optional source artifact ID for versioning (agent continuity/refinement)

        • OptionaltestRunId?: string

          Optional test run ID to link conversation and details to (for test execution traceability)

        • OptionaluserMessage?: string

          The user's message text for this conversation turn (required if conversationDetailId not provided)

      Returns Promise<
          {
              agentResponseDetailId?: string;
              agentResult: ExecuteAgentResult<R>;
              artifactInfo?: CreatedArtifactInfo;
              conversationId: string;
              userMessageDetailId: string;
          },
      >

      Promise containing the agent execution result and conversation/artifact metadata

      const runner = new AgentRunner();
      const result = await runner.RunAgentInConversation({
      agent: myAgent,
      conversationMessages: messages,
      contextUser: currentUser
      }, {
      conversationId: existingConvoId, // Optional - creates new if not provided
      userMessage: 'User query text',
      createArtifacts: true
      });
    • Saves media outputs to AIAgentRunMedia table for permanent storage. Creates a record for each media output promoted during agent execution. All items in the array are saved.

      Parameters

      • agentRunId: string

        The ID of the agent run

      • mediaOutputs: MediaOutput[]

        Array of media outputs to save

      • contextUser: UserInfo

        User context for the operation

      • Optionalprovider: IMetadataProvider

      Returns Promise<string[]>

      Array of saved AIAgentRunMedia IDs

      3.1.0

      const runner = new AgentRunner();
      const mediaIds = await runner.SaveAgentRunMedia(
      agentResult.agentRun.ID,
      agentResult.mediaOutputs,
      currentUser
      );
      console.log(`Saved ${mediaIds.length} media outputs`);
    • Vets one rung of the version-target ladder, returning the plan with a normalized id when the target is usable and null when the caller should fall back.

      Three things are checked, in cost order:

      1. Shape. The id must be a UUID-shaped string. This matters because it is interpolated into the ExtraFilter fragments built by GetMaxVersionForArtifact and CheckForDuplicateVersion; those escape what they are handed, but an id that is not a UUID cannot name an artifact, so rejecting it here turns a doomed query into a clean fallback. The id is then TRIMMED for downstream use — IsValidUUID tolerates surrounding whitespace, so a value with a trailing newline passes validation and would otherwise reach ArtifactID='<uuid>\n' and fail as a SQL conversion error.
      2. Existence — directive-named targets only.
      3. Authorization — directive-named targets only. vwArtifacts carries no per-user predicate and no row-level-security filter, so a row loading successfully proves only that it EXISTS. Without this check an agent could name any artifact id in the instance and have the run's payload appended to it as a new version. The caller must own the artifact or hold an explicit CanEdit grant on it.

      Existence and authorization resolve in ONE RunViews round trip, and via RunView rather than BaseEntity.Load deliberately: Load THROWS on a permission denial, a SQL conversion error or any transient DB fault, and a throw here would propagate to the method-wide catch and lose the whole artifact — the opposite of the graceful fallback this is meant to provide.

      A caller-supplied sourceArtifactId is shape-checked but NOT loaded or authorized: it is a server-side argument rather than model output, and adding a round trip plus a new denial mode to that path would change behavior for every existing agent. (The pre-existing exposure on that path — any authenticated caller may name any artifact id — is unchanged by this PR and wants its own fix.) One exception, and it is deliberate rather than incidental: if the agent named the SAME id first and it failed authorization, rejectedIds refuses it on the caller rung too. The caller path still performs no authorization of its own; it just cannot be used to launder an id this run has already refused.

      Why this does not call PermissionEngine / ArtifactPermissionProvider. That provider (MJCoreEntities/src/custom/PermissionProviders/ArtifactPermissionProvider.ts) answers "may this user Update this artifact" from the permission ROWS, and is the right home for that question in general — see guides/UNIFIED_PERMISSIONS_GUIDE.md §1. It is not used here for two reasons specific to this path. First, it answers only half the question: it reads grant rows and does not treat the artifact's OWNER as an editor, so the UserID check below would remain regardless and the owner half would still live in two places. Second, and decisive: PermissionEngine.CheckPermission returns Allowed: false with "Unknown permission domain" when its domain is not loaded, and this path's response to a denial is a SILENT fallback to creating a new artifact. A deployment that has not synced metadata/permission-domains — or any caller that reaches the runner before PermissionEngine.Config() — would therefore stop versioning for every legitimate editor, with nothing in the logs to say why. A direct query has no such dependency on engine state. As of this writing nothing server-side calls PermissionEngine or runs its Config(): its callers are Explorer's Permissions dashboard, the Sharing Center, and the integration test suite. (Lists/server, Communication/notifications and the MJCoreEntities entity extensions call ResourcePermissionEngine, a different class over MJ: Resource Permissions.)

      The cost of that choice is drift: this is a third answer to "can this user edit this artifact", alongside the provider and ng-conversations' artifact-permission.service.ts. If artifact sharing grows a new grant shape — role grantees, SupportsDeny, cascade from collections — this method must be updated with the provider. Revisit the delegation once PermissionEngine is routinely configured server-side. The cleaner precondition is upstream: CollectionPermissionProvider already treats a collection's owner as a synthetic full-access grantee, and the same treatment in ArtifactPermissionProvider would make it a complete answer, at which point this method should delegate to CheckPermission(..., 'Update') outright rather than compose an owner check around it.

      Parameters

      • plan: { artifactId: string; kind: "version"; source: ArtifactTargetSource }

        The version plan to vet.

      • contextUser: UserInfo

        User the run executes as.

      • md: IMetadataProvider

        Provider for the lookups.

      • agentName: string

        Agent name, for log lines.

      • rejectedIds: Set<string>

        Ids already rejected on a higher rung; mutated with any new rejection so the same id cannot be re-admitted further down the ladder.

      Returns Promise<{ artifactId: string; kind: "version"; source: ArtifactTargetSource }>

      The vetted plan with a trimmed id, or null to fall back.