Member Junction
    Preparing search index...

    ConversationEngine provides centralized, reactive caching for conversations, conversation details (messages), and peripheral data (agent runs, artifacts).

    This engine is the single source of truth for conversation data across all UI consumers (chat area, sidebar, overlay, etc.). It replaces per-component caching that previously lived in conversation-chat-area component, and other scattered locations.

    Usage:

    // Initialize (call once at app startup after metadata is loaded)
    await ConversationEngine.Instance.Config(false, contextUser);

    // Load conversations for the current user
    await ConversationEngine.Instance.LoadConversations('env-id', contextUser);

    // Subscribe to conversation list changes
    ConversationEngine.Instance.Conversations$.subscribe(conversations => {
    // React to changes
    });

    // Load details for a specific conversation
    const details = await ConversationEngine.Instance.LoadConversationDetails('conv-id', contextUser);

    // Get cached details (instant, no DB round-trip)
    const cached = ConversationEngine.Instance.GetCachedDetails('conv-id');

    Hierarchy (View Summary)

    Index

    Constructors

    Accessors

    Methods

    AddDetailToCache AddDynamicConfig AdditionalLoading applyImmediateMutation applyRemoteDelete applyRemoteRecordData ArchiveConversation beginConfigRefresh BuildRunViewParamsForConfig CanEditConversation CanShareConversation canUseImmediateMutation CheckAddToProviderInstances CheckPermissionsOrSkipAll classifyEventForConfig ClearCache cloneEntityForCache Config ConfigEx configFingerprint configLoadedSuccessfully ContextUserCanReadConfigEntity CreateConversation CreateConversationDetail CreateFork DebounceIndividualBaseEntityEvent DeleteConversation DeleteConversationDetail DeleteMultipleConversations DeleteProject emitPropertyChange EnsureConversationLoaded EnsureLoaded findEntityIndexByPrimaryKeys GetAgentContextWindow GetAgentRunForDetail GetAgentRunsMap GetCachedDetailEntry GetCachedDetails GetConfigData GetConversation GetDerivedStateCensus GetGlobalObjectStore GetSharedByInfo GetStateCensus GetVisibleConversationsFilter HandleIndividualBaseEntityEvent HandleIndividualEvent HandleRemoteInvalidateEvent HandleSingleViewResult HandleStartup hasAdditionalLoadingOverride HasCachedDetails HasSweepableConfigs InvalidateConversation isEntityAlreadyInArray isEntityInArrayByRefOrKey isLatestConfigRefresh IsPropertyPermissionConstrained Load LoadConfigs LoadConversationDetails LoadConversations LoadDetailWindow LoadMultipleEntityConfigs LoadProjects LoadSingleConfig LoadSingleDatasetConfig LoadSingleEntityConfig MarkConfigEmptyLoaded MoveConversationToProject MoveMultipleConversationsToProject MoveProjectToParent notifyAlreadyAppliedMutation NotifyDataChange ObserveProperty OnExternalCacheChange payloadMatchesHeldRows PinConversation PinMultipleConversations ProcessEntityEvent ProcessEntityEvents PropertyLoadedSuccessfully RebuildDerivedState RefreshAllItems RefreshConversationDetails RefreshItem RegisterCacheChangeCallbacks RemoveDynamicConfig ResolveConfigIncludeBinaryFields ResolveContextUser SaveConversation SaveConversationDetail scheduleEventRefreshRetry SetAgentRunForDetail SetProvider SetupGlobalEventListener SweepAgainstDatabase syncLocalCacheForConfig TryThrowIfNotLoaded UpdateDetailInCache UpgradeObjectToConfig AnchorRowsFilter ArtifactVersionScopeFilter AssembleContextWindow BranchPathFilterFresh BuildBranchChain BuildBranchPathFilter BuildForkSummaries EditForkPoint FilterRowsToBranchPath FilterToScope ForkDisplayName ForkFirstWords ForkPlainText ForkPointFrom ForkPreviewText getInstance GetProviderInstance HistoryFromFilter IsInScope IsReplacedRow IsRowOnBranchPath LiveRowsFilter LoadBranchesFresh LoadForkSummaries LoadScope LoadWindowRowsFresh NormalizeForkKind RegenerateForkPoint RemoveConnectionInstances ScopeFilter ScopeSubquery SourceRowsFilter TrunkScope

    Constructors

    • While the BaseEngine class is a singleton, normally, it is possible to have multiple instances of the class in an application if the class is used in multiple contexts that have different providers.

      Returns ConversationEngine

    Accessors

    • get AllPropertiesLoadedSuccessfully(): boolean

      Returns true if ALL configured properties loaded successfully. Useful as a quick health check after engine startup.

      Returns boolean

    • get Configs(): BaseEnginePropertyConfig[]

      Returns a COPY of the metadata configs array for the engine. This is a copy so you can't modify the original configs by modifying this array.

      Returns BaseEnginePropertyConfig[]

    • get ContextUser(): UserInfo

      The identity this engine acts as — set during Config() by ResolveContextUser.

      On a server (ProviderType.Database) this is the MJ system user, NOT the caller. Engine caches are process-wide and shared by every user of the process, so an engine acts as the platform rather than as whoever happened to configure it first. Once resolved it does not change for the life of the engine, even across Config(forceRefresh, someUser).

      On a client it is the caller, which is the same thing — a browser hosts one principal.

      Consequences worth knowing when reading this for attribution (UserID stamps on rows an engine writes, run and log ownership): server-side those record the system user. That was already true wherever engines are pre-warmed at startup; it is now true consistently rather than depending on which caller arrived first. Code that needs the acting user for a specific operation should take it as a parameter, not read it from the engine.

      Returns UserInfo

    • get DataChange$(): Observable<EngineDataChangeEvent>

      Observable that emits when any data property changes due to a refresh. Subscribe to this to react to engine data updates (e.g., sync Angular observables).

      Events are emitted after data is refreshed in response to BaseEntity save/delete events. The event includes the full config and the new data array.

      Returns Observable<EngineDataChangeEvent>

      UserInfoEngine.Instance.DataChange$.subscribe(event => {
      if (event.config.PropertyName === 'UserApplications') {
      // Sync local state with engine's updated data
      this.refreshLocalAppList();
      }
      });
    • get DataMapEntries(): ReadonlyMap<string, EngineDataMapEntry>

      Returns a read-only snapshot of all engine property load states. Each entry maps a property name to its load status, including entity/dataset name, row count, success/failure flag, and error message if applicable. Used by dev tools for diagnostics and health monitoring.

      Returns ReadonlyMap<string, EngineDataMapEntry>

    • get EngineDefaultResultType(): "entity_object" | "simple"

      Controls the default RunView ResultType for all entity configs loaded by this engine. Override in subclasses to change the default for the entire engine without modifying each individual config entry.

      • 'entity_object': Full BaseEntity subclass instances (slower, required for .Save()/.Delete())
      • 'simple': Plain JavaScript objects (much faster, suitable for read-only engines)

      Individual configs can still override this via their own ResultType property.

      Returns "entity_object" | "simple"

      'entity_object'
      
    • get EntityEventDebounceTime(): number

      Overridable property to set the debounce time for entity events. Default is 1500 milliseconds (1.5 seconds). This debounce time is used when immediate array mutations cannot be applied (e.g., when Filter, OrderBy, or AdditionalLoading overrides are present) and a full view refresh is required.

      Note: When immediate mutations ARE possible (no Filter, OrderBy, or AdditionalLoading override), updates happen synchronously without any debounce delay.

      Returns number

    • get GlobalKey(): string

      Returns string

    • get IsPermissionConstrained(): boolean

      True when the engine loaded successfully but all entity configs were skipped because the current user lacks read permissions. Accessor properties will throw PermissionConstrainedError if accessed in this state. Check this flag first to degrade gracefully.

      Returns boolean

    • get Loaded(): boolean

      Returns true if the data has been loaded, false otherwise.

      Returns boolean

    • get LoadingSubject(): BehaviorSubject<boolean>

      Returns the loading subject. You can call await Config() and after Config() comes back as true that means you're loaded. However you can also directly subscribe to this subject to get updates on the loading status.

      Returns BehaviorSubject<boolean>

    • get MaxEventRefreshRetries(): number

      Maximum number of retries for an event-triggered config refresh that failed transiently. Overridable by subclasses that want more or less persistence.

      Returns number

    • get PermissionConstrainedEntities(): string[]

      List of entity names that were skipped due to permission denial. Empty if not permission-constrained. Useful for logging/diagnostics.

      Returns string[]

    • get ProviderToUse(): IMetadataProvider

      Returns the metadata provider to use for the engine. If a provider is set via the Config method, that provider will be used, otherwise the default provider will be used.

      Returns IMetadataProvider

    • get ReadonlyConfigs(): readonly Readonly<BaseEnginePropertyConfig>[]

      The engine's metadata configs themselves, not a copy: read them, never change them.

      Configs deep-copies on every read. BaseEngineRegistry.FindCachedEntity() reads the configs of every loaded engine on each call, and runs for every record an IsA parent with an opted-in SubtypeSelector loads, so it reads this instead.

      Returns readonly Readonly<BaseEnginePropertyConfig>[]

    • get RunViewProviderToUse(): IRunViewProvider

      Returns the RunView provider to use for the engine. This is the same underlying object as the

      Returns IRunViewProvider

    Methods

    • Subclasses can override this method to perform additional loading tasks

      Parameters

      Returns Promise<void>

    • Applies an immediate array mutation based on the entity event type. This is faster than running a full view refresh for simple add/update/delete operations.

      On save, the cached entry is a clone owned by this engine's provider — not the saver's entity instance. Storing the saver's instance would pin the saver's provider (often a per-request provider) inside the engine's cache for the engine's full lifetime, which leaks the provider and all its associated state.

      Parameters

      Returns Promise<void>

    • Removes a deleted record from the engine's in-memory arrays using the primary key values from the remote-invalidate event payload. No server round-trip needed.

      Parameters

      Returns boolean

      true if successfully applied to all matching configs, false if fallback is needed

    • Applies record data from a remote-invalidate event directly to the engine's in-memory arrays. Creates a BaseEntity instance, loads the JSON data into it, then updates the matching config arrays — same as applyImmediateMutation but from serialized data instead of a live entity.

      Parameters

      Returns Promise<boolean>

      true if successfully applied to all matching configs, false if fallback is needed

    • Archives a conversation (sets IsArchived = true) and removes it from the active list.

      Parameters

      • id: string

        The conversation ID to archive

      • contextUser: UserInfo

        The current user context

      Returns Promise<boolean>

      true if successful

    • Opens a new full-refresh "generation" for a property and returns its token. Each call bumps the property's monotonic counter, so a token is the latest iff no later refresh for that property has begun since. See _configRefreshGeneration.

      Parameters

      • propertyName: string

      Returns number

    • Builds the RunViewParams for an engine config. Used by LoadSingleEntityConfig, LoadMultipleEntityConfigs, RegisterCacheChangeCallbacks, and syncLocalCacheForConfig to ensure the fingerprint-affecting params (EntityName, ExtraFilter, OrderBy, IgnoreMaxRows, IncludeBinaryFields) are always consistent — preventing cache key mismatches that break cross-server invalidation via Redis pub/sub and local cache upsert/remove operations.

      Row-level security is not part of the fingerprints built from these params. The engine builds them with GenerateRunViewFingerprint(params, connection), without the RLS clause the provider adds for the reading user. On a server that is correct: engines load as the system user, whose RLS clause is empty, so the provider's fingerprint for the same read matches. An engine loaded by an RLS-scoped user (possible on a client) would compute an unscoped fingerprint for a slot the provider stored under a scoped one, so its cache callbacks and syncLocalCacheForConfig would target a different slot. Pass the RLS clause here if such an engine is ever needed.

      Parameters

      Returns RunViewParams

    • True when the user may grant others access to the conversation: they own it, or hold an Owner-level grant on it. Mirrors the server's share gate in MJResourcePermissionEntityExtended, so the UI never offers a share the save would refuse.

      Parameters

      Returns boolean

    • Determines if an immediate array mutation can be used instead of running a full view refresh. Immediate mutations are only safe when:

      1. The config has no Filter (no server-side filtering that might exclude the entity)
      2. The config has no OrderBy (no server-side ordering that would need to be maintained)
      3. The config does not use ResultType='simple' (immediate-mutation pushes BaseEntity instances; arrays loaded as plain objects would become type-mixed)
      4. The subclass has not overridden AdditionalLoading (no post-processing that depends on full data)

      Parameters

      • config: BaseEnginePropertyConfig

        The configuration to check

      • OptionalskipAdditionalLoadingCheck: boolean

        When true, skips the AdditionalLoading override check. Use this when the caller will invoke AdditionalLoading() itself after applying mutations (e.g., applyRemoteRecordData applies all config mutations then calls AdditionalLoading).

      Returns boolean

      true if immediate mutation is safe, false if a full view refresh is needed

    • All-or-nothing permission gate: checks CanRead on every entity config. If ANY entity is denied, ALL configs are skipped — the engine is marked permission-constrained and its data arrays are set to empty []. This prevents noisy permission-denied errors and endless retry loops for users with limited permissions (e.g., org-scoped SaaS roles).

      On the server side with a system user (who has all permissions), this method returns the original configs unchanged — no behavior change for privileged users.

      Parameters

      Returns BaseEnginePropertyConfig[]

      The original configs array (all permissions pass) or an empty array (any denied)

    • Classifies a single entity event against a single config's backing array:

      • 'refresh' — the array does not yet reflect this event's changes; a refresh (or immediate mutation) is required.
      • 'notify' — the array already reflects the change (in-place save of the array's own cached instance, or a manually-pushed create); observers still need a notification.
      • 'silent' — a delete of a row absent from the array; "already spliced" is indistinguishable from "never matched the Filter", so no notification is emitted (manual-splice engine code owns that notification).

      For deletes, the by-key membership check uses the event payload's pre-delete OldValues snapshot — BaseEntity.Delete() calls NewRecord() right after raising the event, which wipes field values and REGENERATES the primary key, so the live entity's key can never match the deleted row by the time the debounced handler runs.

      Returns EntityEventDisposition

    • Configures the engine. Unlike other engines that bulk-load entity tables via BaseEngine.Load(), ConversationEngine manages its own caching because conversations are user-scoped and filtered by environment, which doesn't fit the standard "load all rows" pattern.

      Call this once at startup to initialize the engine. Conversation data is loaded separately via LoadConversations().

      Parameters

      Returns Promise<void>

    • Extended configuration method with object-based options. This provides a more flexible API compared to Config() with positional parameters.

      Internally calls Config() after setting up options that Load() can access.

      Parameters

      Returns Promise<unknown>

      Promise that resolves when configuration is complete

      await MyEngine.Instance.ConfigEx({
      forceRefresh: true,
      contextUser: currentUser
      });
    • True when the config's last load attempt left it in a successfully-loaded state. Reads the same map entry HandleSingleViewResult writes — a transient failure (network, server restart) records loadedSuccessfully=false; a permission denial is recorded as loaded-empty (true) and is deliberately NOT retryable.

      Parameters

      • propertyName: string

      Returns boolean

    • Returns false ONLY when we can positively determine that the effective user lacks Read permission on entityName. Unknown cases (no entity name, no resolvable user, entity not in metadata) return true — so the default is to treat a failure as transient/retryable and server-side system-user loads (full access) are unaffected.

      Used by HandleSingleViewResult to classify a FAILED config load: a load that failed because the user can't read the entity is a PERMANENT condition (a retry will never succeed for this role), so the engine should load that property empty rather than loop on "not marking as loaded" — which is what hangs the Explorer shell for a restricted / app-scoped user (e.g. a magic-link guest). Security is unaffected: the user still receives no data.

      This is a classifier consulted AFTER a failure, never a predictive pre-skip — so a readable entity is always actually queried, and stale/late client permission metadata can never cause a readable entity to be silently skipped.

      Parameters

      Returns boolean

    • This method handles the debouncing process, by default using the EntityEventDebounceTime property to set the debounce time. Debouncing is done on a per-entity basis, meaning that if the debounce time passes for a specific entity name, the events will be processed. This is done to prevent multiple events from being processed in quick succession for a single entity which would cause a lot of wasted processing.

      ALL events raised during the debounce window are buffered and delivered as one batch to ProcessEntityEvents — not just the last one. The refresh-vs-skip decision must be an OR over every coalesced event: judging only the last event would let an already-applied write (e.g., an engine method's in-place save of a cached instance) mask an earlier fresh-instance save the array has never seen.

      Override this method if you want to change how debouncing works, such as having variable debounce times per-entity, etc.

      Parameters

      Returns Promise<boolean>

    • Deletes a conversation from the database and removes it from the cached list.

      Parameters

      • id: string

        The conversation ID to delete

      • contextUser: UserInfo

        The current user context

      Returns Promise<boolean>

      true if successful

      Error if conversation not found or delete fails

    • Deletes a conversation detail and removes it from the cache.

      Parameters

      • conversationId: string

        The conversation this detail belongs to

      • detailId: string

        The detail ID to delete

      • contextUser: UserInfo

        The current user context

      Returns Promise<boolean>

      true if deleted successfully

    • Deletes multiple conversations in a batch operation with per-item error tracking.

      Parameters

      • ids: string[]

        Array of conversation IDs to delete

      • contextUser: UserInfo

        The current user context

      Returns Promise<
          {
              Failed: { Error: string; ID: string; Name: string }[];
              Successful: string[];
          },
      >

      Object with successful and failed deletions

    • Deletes a folder (project) in an FK-safe way. The Conversation→Project and Project→Project (ParentID) foreign keys are RESTRICT, so the row can't be deleted while anything references it. Before deleting, this:

      1. Unassigns every conversation directly in the folder (ProjectID → null).
      2. Reparents direct child folders to this folder's parent (one level up).
      3. Deletes the now-unreferenced folder. Conversations and subfolders are preserved — only the folder itself is removed.

      Note: this does NOT reassign Tasks that reference the project; if a Task still references it, the final delete will fail and this throws with the DB message.

      Parameters

      • id: string

        The project (folder) ID to delete

      • contextUser: UserInfo

        The current user context

      Returns Promise<boolean>

      true if deleted successfully

    • Notifies subscribers of ObserveProperty(propertyName) that the array has changed. No-op if no one has ever observed this property (BehaviorSubject not created). Called from the array mutation sites in BaseEngine.

      Parameters

      • propertyName: string

      Returns void

    • Folds a conversation that was created OUTSIDE this engine (e.g. server-side by a realtime-session mint, which never fires a client BaseEntity event) into the cached list so Conversations$ emits reactively — the sidebar list updates without a manual refresh. Costs at most ONE single-row query, and only when the conversation isn't already cached:

      • Already in the list → no-op (returns the cached entity).
      • Not cached → loads the single row, and (unless it's app-scoped, which lives outside the main-chat list — see CreateConversation) prepends it and re-emits. Returns the loaded entity, or null when the row can't be read.

      Idempotent and safe to call from a session-start hook on every start.

      Parameters

      • id: string

        The conversation ID to ensure is present in the cache

      • contextUser: UserInfo

        The current user context

      Returns Promise<MJConversationEntity>

      The cached/loaded conversation entity, or null when it can't be loaded

    • Ensures the engine is loaded before the caller reads engine state. This is the right call to make at every consumption point — especially for engines registered with @RegisterForStartup({ deferred: true }) whose initial load runs in the background after app boot.

      Idempotent: if the engine is already loaded, returns immediately. If a load is in flight (e.g. the deferred startup or another consumer triggered it), returns the same in-progress promise rather than starting a second load — BaseEngine.Load handles this internally via _loadingSubject.

      Equivalent to this.Config(false) but reads more clearly at call sites:

      await AIEngineBase.Instance.EnsureLoaded();
      const models = AIEngineBase.Instance.Models;

      Parameters

      • OptionalcontextUser: UserInfo

        Optional context user (server-side only)

      • Optionalprovider: IMetadataProvider

        Optional metadata provider override

      Returns Promise<void>

    • Finds an entity in the array by matching all primary key columns. Supports composite primary keys by comparing all PrimaryKey fields from EntityInfo.

      Parameters

      • dataArray: BaseEntity<unknown>[]

        The array of entities to search

      • targetEntity: BaseEntity

        The entity to find (using its primary key values)

      Returns number

      The index of the matching entity, or -1 if not found

    • Builds the agent-facing context window for a conversation.

      When a persisted cross-turn summary exists (the row with the highest Sequence whose SummaryOfEarlierConversation is non-null), the window is [summary-as-message, boundary row (raw), ...tail (raw)] — the summary covers every row with Sequence below the boundary's, so there is no gap and no overlap. When no summary exists, the window is all messages, optionally capped to the most recent maxTailMessages (parity with the legacy sliding-window history load).

      Served from the per-conversation _detailCache: the first touch of a conversation pays one GetConversationComplete query; every subsequent call is a warm in-memory slice kept current by the engine's entity-event handlers.

      Parameters

      • conversationId: string

        The conversation to build the window for

      • contextUser: UserInfo

        User context for the (cold-miss) cache load

      • Optionaloptions: { excludeDetailIds?: string[]; historyFrom?: Date; maxTailMessages?: number }
        • OptionalexcludeDetailIds?: string[]

          Detail rows to omit — e.g. the in-flight agent-response placeholder row created before the agent executes

        • OptionalhistoryFrom?: Date

          A history floor: see AssembleContextWindow.

        • OptionalmaxTailMessages?: number

          With NO summary boundary present: cap the window to the most recent N messages. Ignored when a boundary exists — the summary already covers everything before it, and cutting into the post-boundary tail would create a coverage gap.

      • Optionalscope: ConversationScope

        When given, the window is built from the rows in this scope only; when omitted, from every cached row of the conversation

      Returns Promise<ConversationContextMessage[]>

      Messages in chronological order, each stamped with ConversationContextMetadata

      when the scope belongs to another conversation

    • Returns the full cache entry for a conversation, including all peripheral data (agent runs, artifacts, ratings, user avatars). Returns undefined if not cached.

      This is the primary read method for UI components — returns instant cached data without any database round-trip.

      Parameters

      • conversationId: string

        The conversation ID

      Returns ConversationDetailCache

      The full cache entry, or undefined

    • Retrieves engine-loaded data for a config property by name. This is the canonical accessor for engine getter properties — it checks the data map for permission denial and throws PermissionConstrainedError with the specific denied entity name(s) if the config was skipped.

      Subclasses should use this in every getter that exposes engine-loaded data:

      public get Models(): MJAIModelEntityExtended[] {
      return this.GetConfigData<MJAIModelEntityExtended>('_models');
      }

      Type Parameters

      • E

      Parameters

      • propertyName: string

        The config property name (e.g., '_models', '_agents'), matching the PropertyName used in the engine's Config() params array.

      Returns E[]

      The data array for the property, or an empty array if not yet loaded.

      if the property was skipped due to permission denial.

    • Counts that describe the state a subclass derives in AdditionalLoading — grouped child collections, lookup maps. Override to expose them; the default reports nothing. Must be read-only and must not throw.

      Returns Record<string, number>

    • The Global Object Store is a place to store global objects that need to be shared across the application. Depending on the execution environment, this could be the window object in a browser, or the global object in a node environment, or something else in other contexts. The key here is that in some cases static variables are not truly shared because it is possible that a given class might have copies of its code in multiple paths in a deployed application. This approach ensures that no matter how many code copies might exist, there is only one instance of the object in question by using the Global Object Store.

      Returns GlobalObjectStore

    • Snapshot of what this engine holds: per property, the row count, newest __mj_UpdatedAt, and a hash of the row versions; plus the engine's own derived-state counts. Read-only and cheap enough for diagnostics and startup checks (O(rows)).

      Returns EngineStateCensus

    • Returns an ExtraFilter for MJ: Conversations that matches exactly the conversations LoadConversations shows the user: owned by the user or shared with them, not archived, and (unless includeApplicationScoped) Global or Both scope.

      Use it anywhere that reads conversations for display (search, pickers) so those reads cannot show more than the conversation list does. It is not capped, unlike the list load.

      Parameters

      • environmentId: string

        The environment to filter conversations by

      • contextUser: UserInfo

        The user whose conversations to match

      • Optionaloptions: { includeApplicationScoped?: boolean }

        includeApplicationScoped also matches app-scoped conversations

      Returns Promise<string>

    • Subclasses of BaseEngine can override this method to handle individual MJGlobal events. This is typically done to optimize the way refreshes are done when a BaseEntity is updated. If you are interested in only BaseEntity events, override the HandleIndividualBaseEntityEvent method instead as this method primarily serves to filter all the events we get from MJGlobal and only pass on BaseEntity events to HandleIndividualBaseEntityEvent.

      Parameters

      Returns Promise<boolean>

    • Handles remote-invalidate events from cross-server cache invalidation. These events are fired by GraphQLDataProvider when it receives a cache invalidation notification via GraphQL subscription (originating from Redis pub/sub on another server).

      When the event payload includes recordData (the saved entity as JSON), the engine applies the change directly to its in-memory array — no server round-trip needed. For delete events or events without recordData, falls back to LoadSingleConfig.

      Parameters

      Returns Promise<boolean>

    • All BaseEngine sub-classes get an implementation of IStartupSink so they can be set the auto start in their app container, if desired, simply by adding the

      Parameters

      Returns Promise<void>

      • Config

      decorator. The BaseEngine implementation of IStartupSink.HandleStartup is to simply call

    • Checks if the current instance has overridden the AdditionalLoading method. We do this by comparing the method to the base class's method.

      Returns boolean

      true if AdditionalLoading is overridden, false if using the base implementation

    • Whether this engine holds any config a sweep could act on — an entity config, loaded and readable, whose entity declares TrustServerCacheCompletely = false.

      Exposed so a caller can decide whether the engine is worth the cost of asking. The fleet sweeper takes a cross-process lease per engine per interval; on an installation where nothing declares out-of-band writes, every one of those leases would be claimed only to discover there is nothing to compare. Reading this first is an in-memory check.

      Returns boolean

    • Invalidates (removes) the cached details for a specific conversation. The next call to LoadConversationDetails will fetch fresh data.

      Parameters

      • conversationId: string

        The conversation ID to invalidate

      Returns void

    • Checks if the exact entity object reference is already in the config's data array. Used to skip unnecessary refreshes for UPDATE events where the object was mutated in place.

      Parameters

      Returns boolean

      true if the exact object reference is already in the array

    • Checks if an entity is in the config's data array by object reference OR by primary key match. Used for DELETE events where we need to know if the entity still exists in the array.

      For deletes, pass preDeleteValues (the event payload's OldValues snapshot): by the time the debounced handler runs, BaseEntity.Delete() has already called NewRecord(), which wipes the entity's fields and regenerates its primary key — so a by-key check against the live entity can never match the deleted row. Same hazard (and same OldValues workaround) as LocalCacheManager.HandleBaseEntityEvent.

      Parameters

      • config: BaseEnginePropertyConfig

        The configuration to check

      • entity: BaseEntity

        The entity to look for

      • OptionalpreDeleteValues: Record<string, unknown>

        Pre-delete field snapshot (delete event payload's OldValues)

      Returns boolean

      true if the entity is in the array (by reference or by primary key)

    • True when generation is still the most recent token handed out by beginConfigRefresh for propertyName — i.e. no newer full refresh for this property has started since. A refresh whose token is stale must NOT commit its results: a newer refresh was initiated afterward and read a more-recent state.

      Parameters

      • propertyName: string
      • generation: number

      Returns boolean

    • Check if a specific property was skipped due to permission denial. Forward-compatible with a future partial-loading approach.

      Parameters

      • propertyName: string

      Returns boolean

    • This method should be called by sub-classes to load up their specific metadata requirements. For more complex metadata loading or for post-processing of metadata loading done here, overide the AdditionalLoading method to add your logic.

      Parameters

      Returns Promise<void>

    • Loads the specified metadata configurations.

      Parameters

      • configs: Partial<BaseEnginePropertyConfig>[]

        The metadata configurations to load

      • contextUser: UserInfo

        The context user information

      • OptionalbypassCache: boolean

        When true, bypasses all server-side caching (RunView and dataset) to fetch fresh data directly from the database. Passed through from Load when forceRefresh is true (i.e., Config(true)).

      Returns Promise<void>

    • Loads conversation details using the efficient GetConversationComplete query which returns messages, agent runs, artifacts, ratings, and user avatars in one round-trip. The cache holds every row of the conversation, on every branch path; a scope limits only what this call returns.

      Parameters

      • conversationId: string

        The conversation to load details for

      • contextUser: UserInfo

        The current user context

      • forceRefresh: boolean = false

        If true, reloads even if cached

      • Optionalscope: ConversationScope

        When given, the returned entry holds only the rows in this scope (see scopedEntry); when omitted, every row of the conversation

      Returns Promise<ConversationDetailCache>

      The cache entry with all peripheral data, limited to the scope when one is given

      when the scope belongs to another conversation

    • Loads conversations from the database for the given user and environment. Results are cached and emitted via Conversations$.

      Parameters

      • environmentId: string

        The environment to filter conversations by

      • contextUser: UserInfo

        The current user context

      • forceRefresh: boolean = false

        If true, reloads even if data is already cached

      • Optionaloptions: { includeApplicationScoped?: boolean }

      Returns Promise<void>

    • Loads ONE page of a conversation's transcript — the chat area's windowed history read.

      Additive counterpart to LoadConversationDetails, which stays the FULL-history API. Opening a long conversation must not transfer and hydrate every row, so this reads the newest RawOverread rows below an optional Sequence bound, completes any realtime session the page landed inside, probes whether older rows remain, and loads peripherals for just those rows.

      The result is deliberately NOT written to _detailCache. That cache is keyed by conversation id alone, and GetAgentContextWindow reads it as complete history — a partial entry there would silently starve the agent of everything before the summary boundary, with no error. Use a separate partial cache if incremental caching is needed.

      Every detail read is limited to the path of BranchID (the trunk when omitted). Replaced rows (ReplacedAt set) are never read.

      Does not throw for a read that FAILS — a failed branch-path read, like a failed page read, returns an empty window flagged DetailWindowLoadResult.Failed so the caller can leave its paging state intact rather than mistaking the failure for the start of the conversation. A failed branch-path read never falls back to the trunk.

      It is not, however, exception-proof: the detail reads have no try/catch, so a provider that REJECTS rather than returning Success: false propagates out to the caller. That path is left deliberately — it degrades better than the handled one, because an exception skips the cursor write entirely and the caller's paging state survives untouched.

      This trades ONE fat query for several thin ones. Per page:

      1. BranchPathFilterFresh — one branch read, only when BranchID is set
      2. fetchDetailRowsBySequence — always
      3. expandOldestSession — only when the oldest row is session-stamped
      4. hasOlderDetails — always, in parallel with (4)
      5. buildWindowPeripherals — one batched RunViews of 3
      6. loadWindowUsers — whenever the window references any user 6-7. buildWindowArtifactMap — up to two SEQUENTIAL reads, and it cannot batch them: the version ids come from the junction rows and the artifact ids from the versions.

      That is 3-6 round trips / 5-9 queries per page, against the single GetConversationComplete call that previously covered the ENTIRE conversation.

      For first paint this is unambiguously the right trade — the old call scaled with total conversation length, this one does not. For a reader paging back it inverts: past roughly ten pages the cumulative round trips exceed the old single load.

      The lever for that is PageSize, NOT this method. The per-page cost above is fixed regardless of how many rows come back, so a larger page for OLDER pages amortizes it over more content — and unlike first paint, a reader who has scrolled up has already committed to reading back. If paging up ever feels slow, that is the number to raise.

      Parameters

      • params: LoadDetailWindowParams

        Conversation, optional branch, optional Sequence bound, and page sizing

      • contextUser: UserInfo

        The requesting user (entity RLS applies)

      Returns Promise<DetailWindowLoadResult>

    • Handles the process of loading multiple entity configs in a single network call via RunViews()

      Parameters

      Returns Promise<void>

    • Loads the projects (conversation folders) visible to a user in an environment and emits via Projects$.

      Visible means SHARED (OwnerUserID IS NULL — every folder that existed before ownership was expressible) plus the user's OWN personal folders. A personal folder never reaches anyone else's sidebar. The set is small, so it is cached whole; reloading is skipped only when both the environment AND the user match, because the read now depends on both.

      Parameters

      • environmentId: string

        The environment to filter projects by

      • contextUser: UserInfo

        The current user context; also decides which personal folders load

      • forceRefresh: boolean = false

        If true, reloads even if already cached for this environment/user

      Returns Promise<void>

    • Loads a single metadata configuration.

      Parameters

      • config: BaseEnginePropertyConfig

        The metadata configuration to load

      • contextUser: UserInfo

        The context user information

      • OptionalbypassCache: boolean

        When true, bypasses server-side cache to get fresh data from the database

      • OptionalsuppressEmit: boolean

      Returns Promise<void>

    • Handles the process of loading a single config of type 'entity'.

      Parameters

      • config: BaseEnginePropertyConfig
      • contextUser: UserInfo
      • OptionalbypassCache: boolean

        When true, bypasses server-side cache to get fresh data from the database

      • OptionalsuppressEmit: boolean

      Returns Promise<void>

    • Records a config as successfully loaded with an EMPTY result set. Used when a load failed permanently because the context user lacks Read on the entity: the engine exposes an empty array (not a hang) and is marked loaded so shell boot can complete for a restricted role.

      Parameters

      Returns void

    • Assigns a conversation to a folder (project), or removes it from its folder when projectId is null. Thin wrapper over SaveConversation that keeps the intent explicit at call sites.

      Parameters

      • conversationId: string

        The conversation to move

      • projectId: string

        The target project ID, or null to ungroup

      • contextUser: UserInfo

        The current user context

      Returns Promise<boolean>

      true if saved successfully

    • Moves multiple conversations into a folder (project), or out of every folder when projectId is null. Emits the updated list once for the whole batch.

      Parameters

      • ids: string[]

        Conversation IDs to move

      • projectId: string

        Target folder ID, or null for no folder

      • contextUser: UserInfo

        The current user context

      Returns Promise<ConversationBulkResult>

      Per-item successful and failed outcomes

    • Reparents a folder (project) under another folder, or to the top level when parentId is null. Callers are responsible for preventing cycles (don't pass a descendant of the folder as its new parent). Updates the cached entity in place and re-emits Projects$.

      Parameters

      • projectId: string

        The folder to move

      • parentId: string

        The new parent folder ID, or null for top level

      • contextUser: UserInfo

        The current user context

      Returns Promise<boolean>

      true if saved successfully

    • Emits change notifications for a config whose backing array ALREADY reflects the entity event — e.g., engine code saved the array's own cached instance in place, or manually pushed a newly created entity after Save. In those cases ProcessEntityEvent safely skips the redundant refresh, but the notification must NOT be skipped: without it, DataChange$ and ObserveProperty subscribers (and anything derived from them downstream) never learn the array changed and are stranded on stale state.

      Engine subclasses that manually SPLICE a deleted row out of a config's array must call this themselves ('delete') right after splicing — the debounced event handler cannot distinguish "already spliced" from "never matched this config's Filter", so it stays silent for absent rows.

      Deliberately does not run AdditionalLoading — the skip paths never did, and engines that maintain their arrays manually own any derived-data updates themselves.

      Parameters

      Returns void

    • Notify listeners that a data property has changed. Called automatically by HandleSingleViewResult after data refresh and by applyImmediateMutation for array operations. Subclasses can also call this manually when modifying data arrays directly.

      Parameters

      • config: BaseEnginePropertyConfig

        The configuration for the property that changed

      • data: unknown[]

        The current data array

      • OptionalchangeType: "delete" | "refresh" | "update" | "add"

        The type of change: 'refresh', 'add', 'update', or 'delete'

      • OptionalaffectedEntity: BaseEntity

        For add/update/delete, the entity that was affected

      Returns void

    • Returns an Observable for a specific engine array property. Subscribers receive the current array immediately (BehaviorSubject semantics), then re-receive the same array reference whenever the engine mutates it (save, delete, remote-invalidate, refresh).

      The BehaviorSubject for a property is lazy-created on first call — engines where no one observes a property pay zero runtime cost.

      Type Parameters

      Parameters

      • propertyName: string

        The name of the backing array property on the engine (e.g. _UserNotifications).

      Returns Observable<E[]>

    • True when payloadRows are the same row versions this config currently holds: the same number of rows, and the same multiset of (primary key, __mj_UpdatedAt) pairs.

      Identity is derived from what the engine holds right now, never remembered, so no other assignment path has to keep anything in step with it. (An earlier version remembered the last payload applied and went stale whenever another path replaced the property.) Comparing rows, not the writer's maxUpdatedAt, keeps it independent of how the writer stamped the slot, and catches a delete paired with an insert.

      Returns false — apply the payload — whenever identity cannot be established: the config is not loaded, the entity has no __mj_UpdatedAt column, or a timestamp cannot be read. Known limit: a change that does not bump __mj_UpdatedAt (direct SQL) is invisible here, as it is to the client smart-cache check.

      Parameters

      Returns boolean

    • Toggles or sets the pinned status of a conversation.

      Parameters

      • id: string

        The conversation ID

      • isPinned: boolean

        Whether the conversation should be pinned

      • contextUser: UserInfo

        The current user context

      Returns Promise<boolean>

      true if successful

    • Pins or unpins multiple conversations. Emits the re-sorted list once for the whole batch, so pinned conversations move to the top in a single UI update.

      Parameters

      • ids: string[]

        Conversation IDs to pin or unpin

      • isPinned: boolean

        True to pin, false to unpin

      • contextUser: UserInfo

        The current user context

      Returns Promise<ConversationBulkResult>

      Per-item successful and failed outcomes

    • Back-compat single-event wrapper around ProcessEntityEvents. The debounced pipeline delivers full batches to ProcessEntityEvents — override THAT method to change event-processing behavior; this wrapper exists for subclasses/tests that process one event at a time.

      Parameters

      Returns Promise<void>

    • Does the actual work of processing all entity events coalesced into one debounce window. Not called directly from the event handler because we first debounce the events, which also introduces a delay that is usually desirable so processing happens outside the scope of any transaction processing that originated the events.

      Per matching config, the decision is an OR over the whole batch:

      • If ANY event's changes are not yet reflected in the config's array, run the refresh (or apply each such event via immediate mutation when the config allows it). A single full refresh covers every event in the window.
      • Else, if any event's changes were already applied (in-place save of a cached instance, manual push after create), notify observers once — the refresh is redundant but the notification is not.
      • Deletes of rows absent from the array stay silent: "already spliced by engine code" (that code owns the notification, see notifyAlreadyAppliedMutation) is indistinguishable from "never matched this config's Filter", and notifying would assert phantom deletes to filtered configs' observers.

      A transiently-failed refresh schedules a bounded retry via scheduleEventRefreshRetry — without it, the consumed debounce event would leave observers permanently stale until an unrelated event arrived.

      This is the best method to override if you want to change the actual processing of entity events but do NOT want to modify the debouncing behavior.

      Parameters

      Returns Promise<void>

    • Returns true if the specified property loaded successfully during engine startup. Returns false if the property failed to load (e.g., RunView error) or was never loaded. Consumers can use this to detect partial load failures and trigger recovery.

      Parameters

      • propertyName: string

      Returns boolean

    • Rebuilds derived state after a cache-change event, one rebuild at a time and without letting a failure escape.

      AdditionalLoading is subclass-supplied and may perform database I/O — some overrides call Config() on another engine, which issues queries. Cache-change events are dispatched per fingerprint from a fire-and-forget pub/sub callback, so a burst of them would otherwise run several of those overlapping on the same connection. Serializing them keeps the rebuild off the critical path of whatever else that connection is doing.

      Failures are logged rather than propagated: this runs from an event callback with no caller able to handle them, an unhandled rejection there would be lost anyway, and one engine's rebuild failing must not stop the next event from being applied.

      Parameters

      Returns Promise<void>

    • Refreshes all items

      Returns Promise<void>

    • Refreshes conversation details by re-running the GetConversationComplete query and surgically merging results into the existing cache. Existing objects that haven't changed keep their references (minimizing Angular re-renders).

      • New messages: appended to Details array
      • Existing messages: fields updated in-place on the same object
      • Agent runs: updated in-place or added
      • Artifacts/ratings: replaced per-detail (cheap — plain data, not entity objects)
      • User avatars: merged (new users added)

      If no cache exists yet, falls back to a full load. The cache holds every row of the conversation; a scope limits only what this call returns.

      Parameters

      • conversationId: string

        The conversation to refresh

      • contextUser: UserInfo

        The current user context

      • Optionalscope: ConversationScope

        When given, the returned entry holds only the rows in this scope (see scopedEntry); when omitted, every row of the conversation

      Returns Promise<ConversationDetailCache>

      The updated cache entry, limited to the scope when one is given

      when the scope belongs to another conversation

    • Refreshes a specific item.

      Rebuilds derived state before notifying subscribers: this replaces the property's array, so anything a subclass derived from the previous instances — grouped child collections, memoized lookups — refers to objects the engine has just discarded. This is also the callback the expiration timer fires, so without the rebuild a config with an expiry quietly served stale derived state until something else happened to rebuild it.

      Parameters

      • propertyName: string

        The name of the property to refresh

      Returns Promise<void>

    • Registers cross-server cache change callbacks for entity configs. When another server instance updates cached data for an entity this engine tracks, the engine will automatically reload the affected config.

      This enables multi-server deployments to keep engine in-memory arrays synchronized without polling. Requires a Redis-backed storage provider with pub/sub enabled (via RedisLocalStorageProvider.StartListening).

      Parameters

      Returns void

    • Removes a dynamic metadata configuration at runtime.

      Parameters

      • propertyName: string

        The name of the property to remove

      Returns void

    • Decides which identity this engine acts as and stores it in _contextUser.

      Server-side (ProviderType.Database), an engine acts as the MJ system user — full stop. Engine data is infrastructure: the cache is process-wide and shared by every user of the process, so its contents must not depend on whichever caller reached Config() first. A caller carrying entity-permission denials, RLS row scoping, or field-level denials would otherwise seal a partial, empty, or permission-constrained cache that then serves everyone until the process restarts. Restricting what any given user may SEE stays where it belongs — at the point data is served to them, not at the point it is cached.

      There is deliberately only ONE identity here rather than a separate load-user: every load, refresh, hydration and attribution path reads _contextUser, so a single field makes the rule structurally impossible to violate — a refresh path added later cannot forget to use the right one. The cost is that ContextUser reports the system user on a server; see its docs.

      Client-side (ProviderType.Network) this is a plain assignment: a browser hosts one signed-in principal, and the server already strips whatever that user may not see.

      Degrades, never fails. When no system user can be resolved — no server-side WellKnownUserSource registered, no such row in this database, a failed lookup — the engine simply acts as the caller, exactly as it did before this existed.

      Parameters

      Returns Promise<void>

    • Saves partial updates to a conversation (Name, Description, or any writable field). Loads the entity from DB, applies updates, saves, and updates the in-memory list.

      Parameters

      • id: string

        The conversation ID to update

      • updates: Partial<MJConversationEntity>

        Partial fields to update

      • contextUser: UserInfo

        The current user context

      Returns Promise<boolean>

      true if saved successfully

      Error if conversation not found or save fails

    • Schedules a bounded, backed-off retry of a config refresh that failed transiently during entity-event processing. Without this, one failed RunView after a save would permanently strand every observer on stale data — the debounced event is already consumed, so nothing else re-runs the refresh until an unrelated event for the same entity arrives.

      At most one retry is pending per property at a time; a retry that succeeds notifies observers through the normal HandleSingleViewResult → NotifyDataChange path. Permission denials never reach here (HandleSingleViewResult marks them loaded-empty).

      Parameters

      • config: BaseEnginePropertyConfig

        The config whose refresh failed

      • attempt: number

        1-based attempt number; delays back off linearly (2s, 4s, ...)

      Returns void

    • Adds or updates an agent run in the cache for a specific detail, honoring ShouldReplaceDetailAgentRun so a continuation/sub-agent run never overwrites the primary root orchestrating run for that detail.

      Parameters

      • conversationId: string

        The conversation ID

      • detailId: string

        The detail ID the agent run is associated with

      • agentRun: MJAIAgentRunEntity

        The agent run entity

      Returns void

    • Internal method to set the provider when an engine is loaded. Once this engine instance has a provider bound, subsequent calls are no-ops — preventing transient per-request providers from displacing the persistent provider that first bound to this connection. The cache key is the connection (not the object), so the first persistent provider to load an engine for a connection "owns" the engine for that connection's lifetime.

      Parameters

      Returns void

    • This method is responsible for registering for MJGlobal events and listening for BaseEntity events where those BaseEntity are related to the engine's configuration metadata. The idea is to auto-refresh the releated configs when the BaseEntity is updated.

      Returns Promise<boolean>

    • Compares each loaded entity config with the database — row count and newest __mj_UpdatedAt — and reloads the ones that differ. Finds changes that never raised an MJ event: direct SQL, other applications, restores. Everything else reaches engines through events and does not need this.

      One batched status query for the whole engine. A reloaded config is written to the cache once, so on a shared cache every other server adopts the fresh rows from that one write. Derived state is rebuilt once, after all reloads.

      Limits: an UPDATE that does not change __mj_UpdatedAt or the row count is invisible, as it is to the smart-cache check. Entities without __mj_UpdatedAt are compared by row count only; dataset configs are not checked.

      Returns Promise<EngineSweepResult>

      What was checked and reloaded. Never throws.

    • Syncs an entity change to the LocalCacheManager for a config with CacheLocal enabled. This ensures that IndexedDB/localStorage stays in sync with the engine's in-memory array.

      Only called for configs WITHOUT Filter/OrderBy (immediate mutation path). Filtered/sorted configs use debounced refresh which handles its own caching.

      Parameters

      Returns Promise<void>

    • Helper method for sub-classes to have a single line of code that will make sure the data is loaded before proceeding and will throw an error if not loaded.

      Returns void

    • Pure window assembly over already-loaded rows: exclude → sort by Sequence → summary-boundary fold → tail cap. Extracted from GetAgentContextWindow so SERVER-SIDE callers (agent resolver, cross-turn compaction) can load rows fresh per request — via LoadWindowRowsFresh with the per-request provider and contextUser, applying entity RLS and never populating this engine's process-global detail cache — and still share the exact same fold math the cache-backed client path uses.

      Accepts the minimal ConversationWindowSourceRow shape, satisfied by full entities AND ResultType: 'simple' rows selecting ConversationWindowFields.

      History floor. With options.historyFrom set, the window starts there: rows written before it are dropped, and so is any persisted summary. A summary folds in the conversation from its first message — exactly what the floor excludes — so under a floor none is used, and the window is the most recent maxTailMessages rows at or after the floor. A row whose __mj_CreatedAt is missing or unreadable can't be shown to be after the floor, so it is dropped too.

      Replaced rows (ReplacedAt set) are always left out.

      Parameters

      • details: readonly ConversationWindowSourceRow[]
      • Optionaloptions: { excludeDetailIds?: string[]; historyFrom?: Date; maxTailMessages?: number }

      Returns ConversationContextMessage[]

    • The ExtraFilter predicate for a branch path: the branch's own rows, plus each ancestor's rows up to that ancestor's fork sequence, plus the trunk up to the last fork sequence. A null branch is the trunk alone.

      Column names are bracketed, so the predicate also works inside a subquery on vwConversationDetails from another entity's filter: the PostgreSQL provider converts bracketed names itself but quotes bare names only when they are fields of the outer entity.

      Parameters

      Returns string

    • Edit of a message, or Fork from here on it (the fork replaces it): the new fork starts at the row before it on its path (both null when it is the first message). Undefined when that row is not known: the message is not in pathRows, or it is the first loaded row while older rows exist (hasMoreAbove).

      Parameters

      • message: { ID: string; Sequence: number }
      • pathRows: readonly { BranchID?: string; ID: string; Sequence: number }[]
      • hasMoreAbove: boolean = false

      Returns ForkPoint

    • The first words of a message, for a fork with no name: at most 6 words and 40 characters, '…' when cut.

      Parameters

      • message: string

      Returns string

    • Plain text of a stored message: each @{...} mention becomes @<name>, or is dropped when it has no name, and runs of whitespace become one space. Null when nothing is left.

      Parameters

      • message: string

      Returns string

    • Returns the singleton instance of the class. If the instance does not exist, it is created and stored in the Global Object Store. If className is provided it will be used as part of the key in the Global Object Store, otherwise the actual class name will be used. NOTE: the class name used by default is the lowest level of the object hierarchy, so if you have a class that extends another class, the lowest level class name will be used.

      Type Parameters

      Parameters

      • this: new () => T
      • OptionalclassName: string

      Returns T

    • Returns the cached engine instance for this engine subclass on the connection the given provider points to, creating one if none exists yet. Lookup is keyed by the provider's InstanceConnectionString so multiple provider objects targeting the same connection share a single cached engine.

      Type Parameters

      • T

      Parameters

      Returns BaseEngine<T>

    • The ExtraFilter predicate for a history floor: rows written at or after historyFrom. Shared by every reader that honours a floor, so they agree on what it means.

      Parameters

      • historyFrom: Date

        The first moment that may be read.

      • column: string = '__mj_CreatedAt'

        The timestamp column to compare, when the predicate runs against a view or subquery that names it differently.

      Returns string

      RangeError when historyFrom is an invalid date — a floor that can't be written down must fail rather than silently read everything.

    • True when a rerun of its turn replaced the row (ReplacedAt is set).

      Parameters

      • row: { ReplacedAt?: string | Date }

      Returns boolean

    • filter limited to rows no rerun replaced (ReplacedAt is NULL). A replaced row is an answer a rerun of its turn superseded: it is kept, but never shown and never sent to an agent. Every reader of conversation rows for display or for agent context applies this.

      Parameters

      • filter: string

      Returns string

    • Builds a scope for an explicit branch. An empty, whitespace-only or missing branch id is the trunk. A branch id loads the conversation's branch rows once and must name one of them (compared without regard to case); the scope's BranchID is that row's ID.

      Parameters

      Returns Promise<ConversationScope>

      when the branch rows cannot be read or the branch is not one of them.

    • Loads the window source rows on a conversation's branch path FRESH — through the given provider with the contextUser (entity RLS applies), never touching this engine's process-global detail cache. The trunk costs one RunView; a branch costs two (its branch rows via BranchPathFilterFresh, then the path's rows). The single source of the fresh-load query shape (entity name, filter, order, fields) for every server-side caller: the agent resolver's history loader and the cross-turn compaction pass both consume this, so the two can never drift apart. THROWS on load failure — servers must fail loudly rather than proceed against an empty history. Replaced rows never leave the database.

      Parameters

      • conversationId: string

        The conversation whose detail rows to load

      • contextUser: UserInfo

        The requesting user (entity RLS is applied under this user)

      • Optionalprovider: IMetadataProvider

        Optional per-request metadata provider; falls back to the global default

      • OptionalhistoryFrom: Date

        Optional history floor: only rows written at or after it are loaded, so nothing before it leaves the database. Pair it with the same historyFrom on AssembleContextWindow, which also drops the summary of earlier messages.

      • OptionalbranchId: string

        The branch whose path to load; null or omitted reads the trunk.

      Returns Promise<ConversationWindowSourceRow[]>

      The rows on the branch's path (the trunk when no branch) in Sequence order, shaped for AssembleContextWindow

      When a RunView reports failure (never returns a silent empty set), when historyFrom is an invalid date, when branchId is not among the conversation's branches, or when its chain is longer than MAX_BRANCH_DEPTH

    • The kind of a fork row; a missing or unknown value is 'Fork'.

      Parameters

      • kind: string

      Returns "Edit" | "Fork" | "Regenerate"

    • Removes all cached engine instances for the given connection. Call this when a connection is being torn down (e.g. multi-tenant client logging out) to release the cached engines' memory eagerly. For normal server operation this is rarely needed — the cache is bounded by (distinct connections × engine classes), which is small.

      Parameters

      • connectionKey: string

      Returns void