Skip to content

[RemoteChat] - Integrate running table and all open paths #1488

Description

@JoshuaRowePhantom

Part of #1483

Summary

Implement Commit 5 — Integrate running table and all open paths from the approved remote-agent-session design.

Integrate persisted runtime hydration and local/remote acquisition into IRunningAgentChatTable and every fresh, restore, auto-resume, and non-GUI opening path. Local and remote chats must use the same common IAgentChat surface, all paths must enforce the same persisted-owner decision, and an already-running runtime must never be rehydrated.

Implementation mapping and hierarchy

This child is self-contained for Commit 5. It consumes the contracts supplied by #1484-#1487; it does not recreate those implementations.

Scope

Files

  • Phantom.Workspaces.Services/AcquireAgentChatRequest.cs
  • Phantom.Workspaces.Services/IRunningAgentChatTable.cs
  • Phantom.Workspaces.Services/RunningAgentChatTable.cs
  • Phantom.Workspaces.Services/RunningAgentChatWithEntityInfo.cs
  • Phantom.Workspaces.Llm/RunningAgentChat.cs
  • Phantom.Workspaces.Llm/RunningAgentChatLease.cs
  • Phantom.Workspaces.Llm/AgentServices.cs
  • Phantom.Workspaces.Llm/CurrentSessionContext.cs
  • Phantom.Workspaces.Services/AgentServicesComposition.cs
  • Phantom.Workspaces/ViewModels/OpenAgentSessionShortcutHandler.cs
  • CreateAgentSessionTabForRestoreRequest
  • CreateAgentSessionTabRequest
  • ComposeSessionAgentViewModelOptions
  • AgentViewModelOptions
  • fresh agent-session launcher paths
  • auto-resume paths
  • non-GUI acquisition paths
  • all existing AgentSessionEntityFactory.CreateEntityData callers affected by the property-based factory request

Required behavior

  • Hydrate persisted runtime intent exactly once when a local session is first materialized.
  • Reuse an existing local or remote running entry without rehydrating it.
  • Attach/start remote sessions only through the strict authorized remote-session client/proxy path.
  • Publish a remote running row only after its first authoritative snapshot.
  • Single-flight concurrent acquisition by session, persisted owner, and ownership generation.
  • Leave no running row when acquisition is cancelled or fails before publication.
  • Use the same owner decision and acquisition pipeline for direct open, restore, fresh launch, auto-resume, and non-GUI acquisition.
  • Dispatch continue-in-background changes to the local owner registry or remote client as appropriate, and complete only after persistence and authoritative state publication.
  • Preserve ref-counted viewer leases. Releasing a lease is not explicit termination, although final release may trigger the owner’s default graceful-stop rule.
  • Keep local and remote behavior compatible through IAgentChat.

Relevant product decisions and background

The remote-agent protocol decides which host owns and runs AgentChat. Persisted executor bindings decide where components and tools execute. Effective trust plus MXC decide how a process is confined on its final launch host. These are orthogonal decisions.

Opening a session owned by the current profile starts or reuses the local owning runtime. Opening a session owned by another profile performs an authorized status query and offers:

  1. Connect on owning profile — start there if absent, otherwise attach to the existing runtime.
  2. Resume locally — perform ownership takeover before local acquisition.

The status shown before that choice is Running, Not running, or Unavailable. Unauthorized and missing sessions are both Unavailable and reveal no session metadata. Every opening and restore path applies the same rule.

The owning host constructs the real AgentChat, host services, persistence, tools, and current-session identity. A remote GUI receives an IAgentChat proxy backed by an authoritative snapshot and ordered deltas. It does not construct the owner runtime, execute remote tools locally, compile MXC, or install attachment identity as current-session identity.

ChatClientOverTransport remains the lower-level split-client mechanism; it is not the full-session proxy. No compiled policy, process handle, policy path, or other host-specific runtime state is persisted or sent over machine transport.

continue-in-background is an explicit persisted per-session preference. It defaults to false; a missing legacy value reads as false. Lease disposal releases one viewer. Explicit termination is a separate operation.

API shape convention

  • APIs carrying multiple independent values use a property-based *Request or *Options record plus an optional CancellationToken.
  • Semantically required properties are required init; optional properties have explicit defaults.
  • Requests/options and data values are constructed with named object initializers.
  • Constructors remain compact value objects or receive a small cohesive set of dependencies.
  • Existing framework overrides retain inherited signatures.
  • Protocol DTOs remain property-based records with their existing serializers, kebab-case wire names, strict unknown-member rejection, and fixed codec/concrete-type discriminators. A discriminator is not caller-selectable.
  • The complete approved design contains exactly 28 request/options types: 22 public and 6 internal. This includes the modified AcquireAgentChatRequest, command, UI, and entity-factory requests and the internal authorization/host/runtime requests; it excludes ProcessExecutionRequest owned by [mxc] - Streaming process executor backed by MXC #1474.

Detailed design

Persisted runtime intent and entity factory

AgentSessionRuntimeIntentData is a serialization-only DTO. Its init properties contain no runtime policy:

public sealed record AgentSessionRuntimeIntentData
{
    public string? OwningProfileEntityId { get; init; } = null;
    public long OwnershipGeneration { get; init; } = 0;
    public JsonElement? ExecutorBindings { get; init; } = null;
    public string? TrustProfileReference { get; init; } = null;
    public long? ExpectedTrustProfileRevision { get; init; } = null;
    public bool ContinueInBackground { get; init; } = false;
}

public sealed record PersistedAgentSessionRuntimeIntent
{
    public required string AgentSessionId { get; init; }
    public required string OwningProfileEntityId { get; init; }
    public required long OwnershipGeneration { get; init; }
    public required ExecutorBindings ExecutorBindings { get; init; }
    public string? TrustProfileReference { get; init; } = null;
    public long? ExpectedTrustProfileRevision { get; init; } = null;
    public bool ContinueInBackground { get; init; } = false;
}

public sealed record AgentSessionRuntimeContext
{
    public required PersistedAgentSessionRuntimeIntent Intent { get; init; }
    public ITransportFactoryRegistry? TransportFactoryRegistry { get; init; } = null;
}

public interface IAgentSessionRuntimeContextFactory
{
    AgentSessionRuntimeContext Create(JsonElement agentSessionEntity);
}

public sealed class AgentSessionRuntimeContextFactory
    : IAgentSessionRuntimeContextFactory
{
    public AgentSessionRuntimeContextFactory(
        ITransportFactoryRegistry? transportFactoryRegistry);
}

All new or repeated agent-session entity persistence uses this exact property-based request:

public sealed record CreateAgentSessionEntityDataRequest
{
    public required EntityId AgentDefinitionEntityId { get; init; }
    public required string AgentDisplayName { get; init; }
    public required string AgentSessionId { get; init; }
    public required IReadOnlyCollection<EntityName> AgentSessionNames { get; init; }
    public required DateTimeOffset CurrentTime { get; init; }
    public required string ComputerName { get; init; }
    public required EntityId HostProfileEntityId { get; init; }
    public IReadOnlyDictionary<string, string>? ParameterValues { get; init; } = null;
    public JsonElement? SessionExecutor { get; init; } = null;
    public JsonElement? ExecutorComponentBindings { get; init; } = null;
    public IReadOnlyDictionary<string, JsonElement>? ParameterSelections { get; init; } = null;
    public long OwnershipGeneration { get; init; } = 0;
    public JsonElement? TrustProfileReference { get; init; } = null;
    public long? ExpectedTrustProfileRevision { get; init; } = null;
    public bool ContinueInBackground { get; init; } = false;
}

public static JsonElement CreateEntityData(CreateAgentSessionEntityDataRequest request);

Every call site, including repeated persistence, must create CreateAgentSessionEntityDataRequest with a named initializer and call CreateEntityData(request):

var request = new CreateAgentSessionEntityDataRequest
{
    AgentDefinitionEntityId = agentDefinitionEntityId,
    AgentDisplayName = agentDisplayName,
    AgentSessionId = agentSessionId,
    AgentSessionNames = agentSessionNames,
    CurrentTime = currentTime,
    ComputerName = computerName,
    HostProfileEntityId = currentProfileEntityId,
    ParameterValues = parameterValues,
    ExecutorComponentBindings = executorComponentBindings,
    ParameterSelections = parameterSelections,
    OwnershipGeneration = ownershipGeneration,
    TrustProfileReference = trustProfileReference,
    ExpectedTrustProfileRevision = expectedTrustProfileRevision,
    ContinueInBackground = continueInBackground,
};

var data = AgentSessionEntityFactory.CreateEntityData(request);

JsonSchemas/agent-session.json retains host-profile-entity-id and executor-bindings and adds ownership-generation (integer, minimum zero, default zero), trust-profile-reference, expected-trust-profile-revision (integer, minimum zero), and continue-in-background (boolean, default false). Trust reference and revision are both present or both absent.

HostProfileEntityId is required for every new entity. The factory always writes owner, generation, executor bindings, and background preference. Only the reader accepts a missing owner for the documented legacy migration.

AgentSessionRuntimeContextFactory.Create is the shared hydration seam. It reads owner, generation, trust intent, existing executor bindings, and background preference; reconstructs ExecutorBindings; and returns the process-scoped transport registry. Missing bindings mean local. Malformed bindings or nonlocal bindings without a registry fail closed. It does not resolve GUI routes, fetch a remote runtime, compile MXC, or mutate caller services.

Exact common state contracts

Local and remote chats expose the same atomic state values. These exact property-based forms are required:

public readonly record struct Usage
{
    public Usage() { }
    public long? TotalInputTokenCount { get; init; } = null;
    public long? TotalOutputTokenCount { get; init; } = null;
    public long? TotalCacheReadTokenCount { get; init; } = null;
    public long? TotalCacheWriteTokenCount { get; init; } = null;
    public long? TotalReasoningTokenCount { get; init; } = null;
    public double? TotalSessionCostUsd { get; init; } = null;
}

public readonly record struct AgentInformation
{
    public AgentInformation() { }
    public required string AgentSessionId { get; init; }
    public required string AgentId { get; init; }
    public required string Name { get; init; }
    public required string DisplayName { get; init; }
    public required string Description { get; init; }
    public required bool AcceptsUserInput { get; init; }
    public string? CurrentModelId { get; init; } = null;
    public required AgentDefinition AgentDefinition { get; init; }
}

Unreported usage metrics are null; reported counts and cost are nonnegative. AgentInformation requires nonblank identity/display strings and a complete non-null AgentDefinition; CurrentModelId is null or nonblank. Publishers and codecs validate complete replacement values before one foreground assignment. UsageChanged and InformationChanged fire only after the atomic assignment. Record equality is intentional.

The common surface relevant to acquisition is:

public interface IAgentChat : IAsyncDisposable, IServiceProvider
{
    AgentInformation Information { get; }
    Usage Usage { get; }
    bool IsBusy { get; }
    AgentChatHistoryCollection History { get; }
    Task HistoryPopulated { get; }
    AgentChatRunningItemCollection RunningItems { get; }
    IAgentInputQueues InputQueues { get; }
    ReadOnlyObservableCollection<IRunningSubAgent> SubAgents { get; }
    ReadOnlyObservableCollection<AgentChatModal> Modals { get; }
    ISlashCommandRegistry SlashCommands { get; }
    event EventHandler? InformationChanged;
    event EventHandler? ToolsChanged;
    event EventHandler? UsageChanged;
    event EventHandler<AgentChatHistoryItem>? TurnCompleted;
    IReadOnlyList<AgentChatToolItem> GetToolSnapshot();
    Task SetToolEnabledAsync(string toolId, bool enabled, CancellationToken ct = default);
    Task RespondToModalAsync(
        string modalId, JsonElement response, CancellationToken ct = default);
    void EnqueueSystemNote(string text);
    void EnqueueHelpNote(string text);
    void EnqueueTransientDiagnostic(string text);
    void Interrupt();
}

AgentChat remains sealed and implements IAgentChat; no transport branch is added to it. RemoteAgentChat also implements IAgentChat. Engine-only APIs remain on concrete AgentChat and are not added to the UI contract.

Named remote open contract

Remote open/status/attach uses the existing strict property DTO:

public sealed record AgentSessionOpenRequest
{
    public required int ProtocolVersion { get; init; }
    public required string AgentSessionId { get; init; }
    public required string ExpectedOwningProfileEntityId { get; init; }
    public required long ExpectedOwnershipGeneration { get; init; }
    public required AgentSessionOpenIntent OpenIntent { get; init; }
    public required string AttachmentToken { get; init; }
    public ReplayCursor? ReplayCursor { get; init; } = null;
    public required IReadOnlyList<string> Capabilities { get; init; }
}

var openRequest = new AgentSessionOpenRequest
{
    ProtocolVersion = 1,
    AgentSessionId = agentSessionId,
    ExpectedOwningProfileEntityId = owningProfileEntityId,
    ExpectedOwnershipGeneration = ownershipGeneration,
    OpenIntent = AgentSessionOpenIntent.StartOrAttach,
    AttachmentToken = attachmentToken,
    ReplayCursor = replayCursor,
    Capabilities = capabilities,
};

The codec retains the attach-agent-session descriptor, version-1 kebab-case JSON, strict unknown-member rejection, fixed command/event discriminators, authorization-before-lookup, and owner/generation validation.

Interrupt has this exact client API:

public Task InterruptAsync(Guid commandId, CancellationToken ct = default);

It emits the fixed interrupt command discriminator, is idempotent for the active turn, and does not terminate the runtime.

AcquireAgentChatRequest, running table, and leases

AcquireAgentChatRequest remains the existing public property-based request and adds:

public AgentChatAcquisitionMode AcquisitionMode { get; init; }
    = AgentChatAcquisitionMode.Local;
public ITransport? OwningProfileTransport { get; init; } = null;
public ReplayCursor? ReplayCursor { get; init; } = null;
public enum AgentChatAcquisitionMode
{
    Local,
    AttachRemote,
    StartOrAttachRemote,
}

Example:

var acquireRequest = new AcquireAgentChatRequest
{
    AgentSessionId = new AgentSessionId(agentSessionId),
    AgentSessionEntity = agentSessionEntity,
    AgentServices = agentServices,
    ForegroundScheduler = foregroundScheduler,
    ToolResourceFactory = agentServices.ToolResourceFactory,
    Parameters = parameterValues,
    AgentDefinitionResolver = agentDefinitionResolver,
    EntityName = entityName,
    EntityId = entityId,
    EntityDisplayName = entityDisplayName,
    EntityDescription = entityDescription,
    WorkspaceId = workspaceId,
    AcquisitionMode = AgentChatAcquisitionMode.StartOrAttachRemote,
    OwningProfileTransport = owningProfileTransport,
    ReplayCursor = replayCursor,
};

await runningAgentChatTable.AcquireAsync(acquireRequest, ct);

The added properties are ordinary init-only storage. They perform no I/O and do not clone, own, or dispose the transport. Local forbids OwningProfileTransport. Remote modes require the transport, persisted entity data, owner, and generation.

public interface IRunningAgentChatTable
{
    ObservableCollection<RunningAgentChatWithEntityInfo> RunningSessions { get; }

    Task<RunningAgentChatLease> AcquireAsync(
        AcquireAgentChatRequest request,
        CancellationToken ct = default);

    Task<bool> TerminateAsync(
        AgentSessionId sessionId,
        CancellationToken ct = default);

    Task SetContinueInBackgroundAsync(
        AgentSessionId sessionId,
        bool continueInBackground,
        CancellationToken ct = default);
}

For a not-yet-running local session, AcquireAsync calls AgentSessionRuntimeContextFactory.Create exactly once and immutably merges the result into that session’s AgentServices. Existing running sessions retain their already-composed services.

For remote acquisition, the table creates RemoteAgentSessionClient/RemoteAgentChat through the internal attach path and registers the proxy only after the first authoritative snapshot. Concurrent requests single-flight by session, owner, and generation. Cancellation before publication leaves no row.

TerminateAsync returns false when absent. For a local runtime it disposes the owning runtime; for a remote proxy it sends explicit termination rather than detach.

SetContinueInBackgroundAsync rejects absent and subagent sessions. It dispatches a local owner update to the runtime registry or a remote update to RemoteAgentSessionClient, and completes only after persistence and authoritative publication.

public sealed class RunningAgentChat
{
    public AgentSessionId SessionId { get; }
    public bool IsSubAgent { get; init; }
    public Task<RunningAgentChatLease> AcquireLeaseAsync(CancellationToken ct = default);
}

public sealed class RunningAgentChatLease : IAsyncDisposable
{
    public AgentSessionId SessionId { get; }
    public IAgentChat AgentChat { get; }
    public ValueTask DisposeAsync();
}

public sealed class RunningAgentChatWithEntityInfo
{
    public AgentSessionId SessionId { get; }
    public bool IsSubAgent { get; }
    public bool IsRemote { get; }
    public bool ContinueInBackground { get; }
    public int ViewerCount { get; }
    public Task<RunningAgentChatLease> AcquireLeaseAsync(CancellationToken ct = default);
}

RunningAgentChatLease.AgentChat returns the registered acquired instance. RunningAgentChat and RunningAgentChatWithEntityInfo continue to expose AcquireLeaseAsync; they do not add a borrowed Chat property. Lease disposal is idempotent and releases viewer/reference ownership exactly once.

RunningAgentChatWithEntityInfo metadata is an owner/client-authoritative snapshot. Changes raise the existing property-change path before RunningAgentBrainViewModel.Refresh updates a row.

Session services and current-session identity

AgentServices keeps its existing object-typed layering seams and adds:

public object? AgentExecutionTrustContext { get; init; } = null;
public object? RemoteAgentSessionRuntimeIntent { get; init; } = null;

CurrentSessionContext adds:

public required string OwningProfileEntityId { get; init; }
public required long OwnershipGeneration { get; init; }
public RuntimeEpoch? RuntimeEpoch { get; init; } = null;

Blank owner, negative generation, and an epoch without owner are rejected. The owning host creates one context per runtime. Attachment identity never replaces host identity.

AgentServicesComposition.ComposeSessionServicesAsync remains host-service composition and does not parse persisted executor/trust fields. RunningAgentChatTable applies AgentSessionRuntimeContextFactory.Create afterward through with, preventing GUI/auto-resume divergence and cross-session service leakage.

Unified open and UI helper requests

OpenAgentSessionShortcutHandler.Handle, TryCreateAgentSessionTabForRestoreAsync, TryCreateTabForRestoreAsync, and CreateAgentSessionTabAsync route through one internal persisted-session opening pipeline. ConnectOnOwner constructs remote acquisition; ResumeLocally completes takeover before local acquisition. The handler never parses executor bindings or compiles trust policy.

The inherited Handle and TryCreateTabForRestoreAsync overrides retain their framework signatures. Changed helper APIs use these exact property-based records:

public sealed record CreateAgentSessionTabForRestoreRequest
{
    public required MainWindowViewModel MainWindowViewModel { get; init; }
    public required SubscribedEntityViewModel AgentSessionEntity { get; init; }
    public string? TabId { get; init; } = null;
    public string? Title { get; init; } = null;
    public string? DockRegion { get; init; } = null;
}

public sealed record CreateAgentSessionTabRequest
{
    public required MainWindowViewModel MainWindowViewModel { get; init; }
    public required SubscribedEntityViewModel AgentSessionEntity { get; init; }
    public required IAgentChat AgentChat { get; init; }
}

public sealed record ComposeSessionAgentViewModelOptions
{
    public required MainWindowViewModel MainWindowViewModel { get; init; }
    public required ObservableLoggerFactory LoggerFactory { get; init; }
    public required IAgentChat AgentChat { get; init; }
    public required SubscribedEntityViewModel AgentSessionEntity { get; init; }
    public required AgentSessionWorkspaceTabViewModel Tab { get; init; }
    public required TaskScheduler ForegroundScheduler { get; init; }
}

public sealed record AgentViewModelOptions
{
    public required IAgentChat AgentChat { get; init; }
    public required string DisplayName { get; init; }
    public required string Description { get; init; }
    public required ObservableLoggerFactory LoggerFactory { get; init; }
    public required TaskScheduler ForegroundScheduler { get; init; }
    public AgentViewModel? ParentAgentViewModel { get; init; } = null;
}
public Task<AgentSessionWorkspaceTabViewModel?> TryCreateAgentSessionTabForRestoreAsync(
    CreateAgentSessionTabForRestoreRequest request,
    CancellationToken ct = default);

public Task<AgentSessionWorkspaceTabViewModel> CreateAgentSessionTabAsync(
    CreateAgentSessionTabRequest request,
    CancellationToken ct = default);

public AgentViewModel ComposeSessionAgentViewModel(
    ComposeSessionAgentViewModelOptions options);

public AgentViewModel(AgentViewModelOptions options);
public IAgentChat AgentChat { get; }

Named initializer examples:

var restoreRequest = new CreateAgentSessionTabForRestoreRequest
{
    MainWindowViewModel = mainWindowViewModel,
    AgentSessionEntity = agentSessionEntity,
    TabId = tabId,
    Title = title,
    DockRegion = dockRegion,
};

var restoredTab = await handler.TryCreateAgentSessionTabForRestoreAsync(
    restoreRequest,
    ct);

var createTabRequest = new CreateAgentSessionTabRequest
{
    MainWindowViewModel = mainWindowViewModel,
    AgentSessionEntity = agentSessionEntity,
    AgentChat = agentChat,
};

var tab = await handler.CreateAgentSessionTabAsync(createTabRequest, ct);

var composeOptions = new ComposeSessionAgentViewModelOptions
{
    MainWindowViewModel = mainWindowViewModel,
    LoggerFactory = loggerFactory,
    AgentChat = agentChat,
    AgentSessionEntity = agentSessionEntity,
    Tab = tab,
    ForegroundScheduler = foregroundScheduler,
};

var agentViewModel = handler.ComposeSessionAgentViewModel(composeOptions);

var viewModelOptions = new AgentViewModelOptions
{
    AgentChat = agentChat,
    DisplayName = displayName,
    Description = description,
    LoggerFactory = loggerFactory,
    ForegroundScheduler = foregroundScheduler,
};

var editor = new AgentViewModel(viewModelOptions);

Fresh launch, restore, and auto-resume pass the persisted entity into acquisition. Non-GUI acquisition uses the same table seam. All paths preserve slash-command composition through the common IAgentChat surface and cannot bypass runtime hydration or owner selection.

Out of scope

Tests

All prior tests remain required. The following committed-design tests are also part of this child where they define Commit 5, its entity-factory boundary, common acquisition surface, open DTO, UI helper requests, or authoritative running metadata.

AgentSessionRuntimeContextFactoryTests (Phantom.Workspaces.Tests)

  • Constructor_NullRegistry_AllowsLocalOnlyHydration.
  • AgentSessionRuntimeIntentData_InitProperties_PreserveOnlyPersistableIntent.
  • CreateAgentSessionEntityDataRequest_RequiredInitProperties_AreMarkedRequired.
  • CreateAgentSessionEntityDataRequest_OptionalProperties_UseDocumentedDefaults.
  • CreateEntityData_NamedInitializer_PersistsMappedFields.
  • PersistedAgentSessionRuntimeIntent_Init_InvalidOwnerOrGeneration_RejectsValue.
  • AgentSessionRuntimeContext_Init_StoresIntentAndProcessRegistry.
  • Create_PersistedSplitBindings_ReconstructsRuntimeContext.
  • Create_NoPersistedBindings_UsesLocalDefaults.
  • Create_LegacyHostProfile_UsesSessionExecutorFallback.
  • Create_MalformedBinding_ReportsBindingKeyWithoutValue.
  • Create_NonlocalBindingWithoutRegistry_ReportsConfigurationError.
  • Create_TrustIntent_PreservesReferenceAndExpectedRevision.
  • AgentSessionEntityFactory_CreateEntityData_DefaultBackground_PersistsFalse.
  • AgentSessionEntityFactory_CreateEntityData_BackgroundEnabled_PersistsTrue.
  • AgentSessionEntityFactory_CreateEntityData_DefaultOwner_ThrowsArgumentException.
  • AgentSessionEntityFactory_CreateEntityData_RuntimeAuthority_PersistsOwnerGenerationBindingsAndTrust.
  • Create_MissingContinueInBackground_UsesFalse.
  • Create_ExplicitContinueInBackground_PreservesTrue.
  • Create_CompiledPolicyProperty_RejectsEntity.

RunningAgentChatTableTests (Phantom.Workspaces.Tests)

  • AcquireAsync_NewLocalSession_HydratesServicesBeforeFactoryAcquisition.
  • AcquireAsync_ExistingLease_DoesNotRehydrateRuntimeContext.
  • AcquireAsync_RemoteMode_UsesInternalAttachPath.
  • AcquireAsync_CancelledBeforePublication_AddsNoRunningRow.
  • AcquireAsync_ValidRemoteSession_AddsProxyAfterSnapshot.
  • AcquireAsync_RemoteMissingEntityOrTransport_ThrowsArgumentException.
  • AcquireAsync_ConcurrentSameRemoteRuntime_ReturnsLeasesForOneProxy.
  • AcquireAsync_RemoteAuthorizationDenied_AddsNoRunningRow.
  • TerminateAsync_MissingSession_ReturnsFalse.
  • TerminateAsync_LocalSession_DisposesOwningRuntime.
  • TerminateAsync_RemoteSession_SendsTerminateNotDetach.
  • SetContinueInBackgroundAsync_LocalOwner_PersistsAndPublishesPreference.
  • SetContinueInBackgroundAsync_RemoteProxy_SendsCommandAndAwaitsEvent.
  • SetContinueInBackgroundAsync_MissingOrSubagentSession_ThrowsArgumentException.
  • SetContinueInBackgroundAsync_CancelledBeforeWrite_DoesNotPersistOrPublish.
  • RunningSessions_RemoteAttach_MutatesOnForegroundScheduler.

CurrentSessionContextTests and AgentServicesTests

  • CurrentSessionContext_ValidOwnerGenerationEpoch_PreservesOwningHostIdentity.
  • CurrentSessionContext_BlankOwner_RejectsInitialization.
  • CurrentSessionContext_NegativeGeneration_RejectsInitialization.
  • CurrentSessionContext_AttachmentPeer_DoesNotReplaceHostIdentity.
  • AgentServices_AgentExecutionTrustContextSetter_WithExpressionPreservesOtherServices.
  • AgentServices_RemoteRuntimeIntentSetter_WithExpressionPreservesOtherServices.
  • AgentServices_GetService_NewObjectTypedSeams_DoesNotExposeConcreteTypes.
  • AcquireAgentChatRequest_RemoteInitProperties_PreserveModeTransportAndCursor.
  • AcquireAgentChatRequest_Defaults_SelectLocalModeWithoutTransportOrCursor.
  • AcquireAgentChatRequest_InvalidModeCombination_AcquireRejectsRequest.
  • AgentViewModel_AgentChatProperty_LocalAndRemote_ReturnsIAgentChat.
  • RunningAgentChatLease_AgentChatProperty_LocalAndRemote_ReturnsIAgentChat.
  • RunningAgentChatWithEntityInfo_RetentionMetadataChange_RaisesAuthoritativeUpdate.

Common Usage, AgentInformation, and IAgentChat contract tests

  • Information_LocalChat_ReturnsAtomicAgentInformation.
  • Usage_LocalChat_ReturnsAtomicUsage.
  • InputQueues_LocalChat_ReturnsCommonQueueAggregate.
  • GetToolSnapshot_MutationAfterRead_DoesNotChangeReturnedSnapshot.
  • SetToolEnabledAsync_KnownTool_ChangesStateThenRaisesToolsChanged.
  • SetToolEnabledAsync_UnknownTool_ThrowsArgumentException.
  • SetToolEnabledAsync_Cancelled_DoesNotMutateOrRaiseEvent.
  • RespondToModalAsync_CurrentModal_AcceptsExactlyOnce.
  • RespondToModalAsync_UnknownModal_ThrowsArgumentException.
  • EnqueueSystemNote_ValidText_AppendsSystemNote.
  • EnqueueHelpNote_ValidText_AppendsHelpNote.
  • EnqueueTransientDiagnostic_ValidText_AppendsNonPersistedDiagnostic.
  • Interrupt_ActiveTurn_CancelsTurnWithoutDisposingChat.
  • Interrupt_NoActiveTurn_IsIdempotent.
  • Usage_EqualValues_CompareEqual.
  • Usage_DefaultInitialization_AllOptionalMetricsAreNull.
  • Usage_NamedInitializer_PreservesExactMetricTypes.
  • Usage_RoundTrip_PreservesNullableCountsAndDoubleUsd.
  • UsagePublisher_NegativeMetric_RejectsBeforePublication.
  • UsageChanged_CompleteReplacement_StateVisibleBeforeSingleEvent.
  • AgentInformation_EqualValues_CompareEqual.
  • AgentInformation_RequiredInitProperties_AreMarkedRequired.
  • AgentInformation_NamedInitializer_PreservesAllFields.
  • AgentInformationPublisher_InvalidRequiredString_RejectsBeforePublication.
  • AgentInformationPublisher_InvalidOptionalModel_RejectsBeforePublication.
  • AgentInformation_AuthorizedPeer_RoundTripsCompleteDefinition.
  • AgentInformationPublisher_NullDefinition_RejectsBeforePublication.
  • SessionSnapshot_TwoAuthorizedViewers_ReceiveEquivalentFullDefinition.
  • OpenAsync_UnauthorizedPeer_SerializesNoSessionMetadata.
  • InformationChanged_SessionAndModelChange_StateVisibleBeforeSingleEvent.
  • TurnCompleted_TurnPersists_EventRaisedAfterHistoryMutation.
  • DisposeAsync_RepeatedCall_DisposesOnce.
  • GetService_KnownService_ReturnsExistingService.

Remote open/client contract tests relevant to acquisition

  • ClientRequestTypes_RequiredInitProperties_AreMarkedRequired.
  • ClientRequestTypes_NamedInitializers_PreserveStatusAndCommandPayloads.
  • GetStatusAsync_AuthorizedRunningOrStopped_ReturnsAuthoritativeStatusOnly.
  • GetStatusAsync_UnauthorizedOrMissing_ReturnsUnavailableWithoutMetadata.
  • ConnectAsync_FirstCall_OpensAttachAgentSessionChannel.
  • ConnectAsync_SecondCall_ThrowsInvalidOperationException.
  • ConnectAsync_CancelledBeforeOpen_LeavesClientDisconnected.
  • ConnectAsync_SnapshotThenDelta_UpdatesCursorAndRaisesFramesInOrder.
  • ConnectAsync_SequenceGap_ClosesWithProtocolException.
  • ConnectAsync_UnknownDiscriminator_ClosesWithProtocolException.
  • ReconnectAsync_UnexpectedLoss_ForcesAttachWithTokenAndLastAppliedCursor.
  • ReconnectAsync_ConnectedDetachedTerminalOrExpired_ThrowsInvalidOperationException.
  • ReconnectAsync_CancelledAttempt_AllowsRetryBeforeDeadline.
  • InterruptAsync_Connected_SerializesInterruptAndAwaitsCorrelation.
  • SetContinueInBackgroundAsync_Connected_AwaitsPersistedAuthoritativeEvent.
  • SetContinueInBackgroundAsync_Rejected_LeavesProjectionUnchanged.
  • CommandMethod_EmptyCommandId_ThrowsArgumentException.
  • CommandMethod_CancelledAfterWrite_DoesNotRetractCommand.
  • CommandMethod_NotConnected_ThrowsInvalidOperationException.
  • FrameReceived_ValidFrame_CursorAdvancesBeforeSubscriberRuns.
  • LastAppliedCursor_NoFrames_IsNull.
  • RuntimeEpoch_EmptyValue_RejectsInitialization.
  • ReplayCursor_NegativeSequence_RejectsInitialization.
  • AgentSessionOpenRequest_InvalidVersionOrGeneration_RejectsInitialization.
  • ProtocolDtos_RequiredInitProperties_AreMarkedRequired.
  • ProtocolDtos_OptionalProperties_UseDocumentedDefaults.
  • ProtocolDtos_NamedInitializers_PreserveFixedDiscriminators.
  • Serialize_AllOpenIntents_UsesVersionOneDiscriminators.

AgentViewModelTests

  • Constructor_RemoteChat_UsesCommonSurfaceWithoutConcreteCast.
  • AgentViewModelOptions_NamedInitializer_PreservesRequiredValuesAndParentDefault.
  • Constructor_WrongForegroundContext_Throws.
  • RespondToModalAsync_CurrentModal_SendsResponseAndKeepsInputGatedUntilDismissed.
  • RespondToModalAsync_UnknownModal_ThrowsArgumentException.
  • RespondToModalAsync_Cancelled_DoesNotDismissModal.
  • ModalEvent_DescendantModal_UpdatesRootAggregateOnly.
  • DisposeAsync_RemoteChat_UnsubscribesBeforeDetaching.
  • InterruptCommand_RemoteChat_InvokesCommonInterrupt.
  • ConfigureSlashCommands_RemoteChat_RegistersOnlyCommonHandlers.

SlashCommandContextTests

  • AgentChatSetter_LocalOrRemote_PreservesCommonChat.
  • AgentChatSetter_Null_RejectsInitialization.

OpenAgentSessionShortcutHandlerTests

  • Handle_OwnerIsCurrentProfile_AcquiresLocalRuntime.
  • Handle_OwnerIsRemoteConnectChoice_AttachesOnOwner.
  • Handle_OwnerIsRemoteResumeChoice_CompletesTakeoverBeforeLocalAcquire.
  • TryCreateAgentSessionTabForRestoreAsync_RemoteOwner_UsesSameChoicePipeline.
  • TryCreateTabForRestoreAsync_RemoteOwner_UsesSameChoicePipeline.
  • Handle_RemoteOwnerPrompt_ShowsAuthorizedRunningStatus.
  • Handle_RemoteOwnerPrompt_ShowsAuthorizedNotRunningStatus.
  • Handle_RemoteOwnerPrompt_UnauthorizedOrMissingShowsUnavailable.
  • CreateAgentSessionTabAsync_PersistedSession_PassesEntityToAcquisition.
  • ComposeSessionAgentViewModel_RemoteChat_ConfiguresCommonSlashCommandSurface.
  • SessionUiRequestOptions_NamedInitializers_PreserveRequiredValuesAndOptionalDefaults.
  • DisposeAsync_InitializationInFlight_CancelsWithoutPublishingReadyTab.

Fresh, restore, auto-resume, and non-GUI parity

  • Direct open, restore, fresh launch, auto-resume, and non-GUI acquisition all pass persisted entity data into AcquireAsync.
  • Each path applies the same local/connect-on-owner/resume-local decision.
  • Each path uses named request/options initialization.
  • Each path acquires the same single-flight runtime for the same session, owner, and generation.
  • Existing runtime reuse does not invoke hydration again.
  • Cancellation before ready publication leaves no tab or running row published as ready.
  • Common slash-command composition is present for both local and remote chats.

Acceptance criteria

  • Every affected creation/persistence call uses a named CreateAgentSessionEntityDataRequest variable and AgentSessionEntityFactory.CreateEntityData(request).
  • No superseded positional entity-factory or UI-helper signature remains.
  • Usage and AgentInformation exactly match the property contracts above.
  • Remote open and protocol records retain their serializer and fixed-discriminator behavior.
  • Client interrupt uses InterruptAsync(Guid commandId, CancellationToken ct = default).
  • All open paths use property-based request/options records and the common acquisition pipeline.
  • Local and remote table acquisition, termination, retention updates, cancellation, and single-flight behavior pass the named tests above.
  • The request/options audit remains exactly 28 total: 22 public and 6 internal.
  • No behavior owned by [RemoteChat] - Persist and hydrate runtime intent #1484-[RemoteChat] - Add authorization, host, and viewer lifecycle #1487 is duplicated or weakened.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

verified-locallyImplementation has been verified locally

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions