Member Junction
    Preparing search index...

    BaseSingleton background reconciler that keeps the durable AIAgentSession state from drifting away from volatile process reality. A crash/redeploy vaporizes a host's in-memory sockets but leaves its session rows reading Active/Idle forever; the janitor force-closes those orphans.

    Three close sweeps, all writing through SessionManager.CloseSession so Record Changes captures each transition and channel rows are disconnected consistently:

    1. Own-instance recovery (RunStartupRecovery) — run once at boot. Closes any Active/Idle session whose HostInstanceID belongs to an earlier boot of this MJAPI instance (same host and port, different boot; see HostInstanceIdentity). Another MJAPI on the same host serves on another port, so its live sessions are left alone. Primary defense against the "Active forever" leak after a restart.
    2. Global staleness sweep (RunStalenessSweep) — run on every periodic tick, on every instance. Closes any Active/Idle session whose LastActiveAt is older than closeThresholdMinutes, regardless of host. Catches sessions whose owner died without a clean reboot (OOM, scaled-down pod).
    3. Max-duration sweep (RunMaxDurationSweep) — run on every periodic tick after the staleness sweep. Closes any session past its stored hard deadline (the public voice cost cap).

    The close sweeps stamp CloseReason = 'Janitor'. A shutdown-time path — RunShutdownDrain, invoked from Shutdown during the graceful ShutdownRegistry drain — closes this exact host instance's own live sessions with CloseReason = 'Shutdown'.

    Separately from closing, a recording recovery pass runs after the close sweeps on every periodic tick, without blocking them, and closes nothing. It purges shard-ledger rows past the lookback window, then RunRecordingLedgerSweep assembles the recordings of Closed sessions on the shard ledger that no terminal signal (or a dead assembler) left unassembled, then RunRecordingRecoverySweep rebuilds, from legacy storage segments, the recording of Closed sessions not on the ledger whose end-of-call upload never arrived.

    The close sweeps are idempotent and safe to run concurrently on every instance: closing an already-Closed session is a no-op, and the close path is last-writer-wins. Ledger assembly is arbitrated by the ledger's atomic claim. Legacy recovery is not: two instances can recover the same session at once (there is no claim column for it). Each attempt uploads its own file and defers to an already-stamped recording on a best-effort basis, so the cost is duplicate work and an extra file, not a lost recording. The extra file is an orphan when the second attempt's re-check sees the first stamp; when both re-checks pass first, both files are stamped in turn and both are linked to the session (the later stamp wins RecordingFileID). Every sweep pages with keyset (AfterKey) pagination per the deep-pagination guide so a large backlog can't blow up memory.

    Hierarchy (View Summary)

    Implements

    Index

    Constructors

    Properties

    ShutdownName: "SessionJanitor" = 'SessionJanitor'

    Identifier surfaced in graceful-shutdown logs.

    Accessors

    • get GlobalKey(): string

      Returns string

    Methods

    • Overrides default tuning. Safe to call before or after Start. Every value given must be a finite number greater than zero: a NaN or non-positive duration would otherwise build an invalid cutoff date and make every later sweep throw. All-or-nothing: on an invalid value it throws, naming the field and value, and applies nothing. undefined values are ignored.

      Parameters

      Returns void

    • 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

    • Server-authoritative MAX-DURATION enforcement (public web-widget voice cap, public-web-widget.md W4). Closes any Active/Idle session whose stored absolute deadline (Config_.maxSessionDeadlineIso) has passed — a hard wall-clock ceiling that fires regardless of activity, so a public voice guest cannot run a session indefinitely (cost-bombing) even if the client ignores its own abuse guard.

      Only sessions that CARRY a deadline are loaded (a cheap Config_ LIKE pre-filter), then the exact ISO deadline is parsed and compared in JS (Config_ is JSON, not a queryable column). Stamped CloseReason = 'Janitor', idempotent + concurrency-safe like the other sweeps. Returns the count closed.

      Parameters

      Returns Promise<number>

    • Backstop for client-direct recordings on the shard ledger: assembles the recording of Closed sessions that closed between the lookback window and the grace period and are still

      • Pending (shards, or a terminal signal, with no assembly since: the browser died before its terminal request, or the in-process assembly request was lost with its host),
      • Assembling with no write for recordingRecoveryAttemptTimeoutMinutes (the assembler died, or it hit a transient error and left the claim for a re-claim), or
      • status-less while the ledger holds shards for them (see restoreLostRecordingStatus).

      Each candidate is claimed with the ledger's atomic, stale-aware claim (a lost claim is skipped: another assembler has it) and assembled with ForcePartial, so missing shards become silence. An attempt is bounded by recordingRecoveryAttemptTimeoutMinutes and shares the legacy sweep's abandoned-attempt pause; failures back off and are given up on exactly like legacy recovery, in their own tracker. At most MAX_RECOVERIES_PER_SWEEP assemblies per call. Never throws for a single bad session. Returns the number of recordings assembled (Complete or Partial).

      Parameters

      Returns Promise<number>

    • Rebuilds the recording of sessions whose end-of-call upload never arrived (browser died, tab closed). A session is a candidate when it is Closed, has RecordingStartedAt (consent is implied: recording only starts after the guest agreed), still has no RecordingFileID, and closed between the lookback window and the grace period (see SessionJanitorConfig.recordingRecoveryGraceMinutes).

      Work is bounded: candidates are handled sequentially, at most MAX_RECOVERIES_PER_SWEEP recovery attempts per call, and a failing session backs off exponentially (min(2^k * 5 min, 6 h)) and is abandoned after MAX_RECOVERY_ATTEMPTS failures with one LogError. Backoff entries of sessions that left the candidate set are pruned, but only after a pass that saw every candidate, so a capped pass never forgets sessions it did not reach.

      Sessions on the shard ledger (a RecordingStatus, or any ledger row) are left to RunRecordingLedgerSweep: their audio is in SQL, not in storage segments.

      Never throws for a single bad session. Returns the number of recordings recovered.

      Parameters

      Returns Promise<number>

    • IShutdownable: stop the timer, then drain this host's own live sessions so a graceful stop never strands Active/Idle rows for the next boot's janitor to mop up, then wait (at most RECOVERY_SHUTDOWN_WAIT_MS) for an in-flight recording recovery pass. Drained sessions are stamped CloseReason = 'Shutdown' (vs. 'Janitor' for crash orphans), so the dashboards can tell a clean redeploy from a reconciled crash. Never throws; the drain is skipped when Start was never called (no captured provider/user).

      Returns Promise<void>

    • Run own-host startup recovery once, then schedule the periodic tick (staleness and max-duration close sweeps, then recording recovery). Idempotent: a second call does not stack a second timer. Captures the provider + system user so the timer callback can run without re-supplying them, and re-enables recording recovery after a Stop. Throws, before doing any work, when intervalMs is invalid (see Configure). Takes the server's database provider because recording recovery calls the shard ledger's stored procedures.

      Parameters

      Returns Promise<void>

    • 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