Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
3b649ab
Add WithTerminal API: TerminalAnnotation, TerminalHostResource, and e…
mitchdenny Apr 20, 2026
3abd3b1
Add Aspire Terminal Protocol spec, shared codec, and playground
mitchdenny Apr 20, 2026
7ce3362
Add terminal protocol test client and verify end-to-end
mitchdenny Apr 20, 2026
38d6785
Add DCP model TerminalSpec, backchannel RPC, and CLI terminal command
mitchdenny Apr 20, 2026
9277b2d
Add WithTerminal custom socket path provider overload
mitchdenny Apr 20, 2026
2b04243
Add Dashboard terminal support: xterm.js, WebSocket proxy, ConsoleLog…
mitchdenny Apr 20, 2026
f777479
Fix Dashboard terminal: use script tags for xterm.js and int IDs for …
mitchdenny Apr 20, 2026
50479bc
Add production Aspire.TerminalHost and wire into AppHost discovery
mitchdenny Apr 20, 2026
ec0c756
Fix playground: use custom socket path overload to avoid dual termina…
mitchdenny Apr 20, 2026
c95cd84
Redesign terminal host for reconnection with state replay
mitchdenny Apr 20, 2026
a821875
WithTerminal Phase 1: refactor to per-replica UDS pair design
mitchdenny May 4, 2026
72e784e
WithTerminal Phase 2: Aspire.TerminalHost on Hex1b HMP v1
mitchdenny May 4, 2026
875a550
WithTerminal Phase 4: wire per-replica TerminalSpec into DCP Executab…
mitchdenny May 4, 2026
8c14060
WithTerminal Phase 5: backchannel exposes per-replica terminal endpoints
mitchdenny May 5, 2026
74c39b2
WithTerminal Phase 6: aspire terminal CLI command on Hex1b HMP v1
mitchdenny May 5, 2026
169456f
Phase 7: Dashboard /api/terminal WebSocket proxy + per-replica Termin…
mitchdenny May 5, 2026
f39ee87
Phase 8: WithTerminal end-to-end playground + plain-executable fix
mitchdenny May 5, 2026
174eeee
Phase 8 fix: TerminalView reacts to resource/replica parameter changes
mitchdenny May 5, 2026
6c37e6e
WithTerminal: container support + Node.js playground
mitchdenny May 5, 2026
08c3615
Terminals playground: nodebox fix - override CMD instead of entrypoint
mitchdenny May 5, 2026
d6f429d
Flip producer-side connection direction in Aspire.TerminalHost
mitchdenny May 5, 2026
b1214f4
Dashboard terminal: send resize on WebSocket open before render
mitchdenny May 5, 2026
17fdd7e
WithTerminal: process-restart support + Stop-regression hardening (Ph…
mitchdenny May 5, 2026
813b0b0
Phase 11: CLI wires up HMP1 multi-head primary/secondary protocol
mitchdenny May 6, 2026
33036bf
TerminalHost: bridge consumer-side resize to raw DCP HMP1 frame
mitchdenny May 8, 2026
efb6837
Update Hex1b to 0.147.0 + transient nuget.org Hex1b-only mapping
mitchdenny May 8, 2026
2dc81db
Phase 17 Dashboard: transplant WebMuxerDemo terminal chrome + dumb by…
mitchdenny May 8, 2026
a1f4b44
Phase 17 CLI: split TerminalCommand and lift WebMuxerDemo viewer expe…
mitchdenny May 8, 2026
dc10147
Terminals playground: shell2 control resource for Stop bisection + sl…
mitchdenny May 8, 2026
33d2b48
Update Hex1b 0.147.0 -> 0.150.0; drop transient nuget.org mapping
mitchdenny May 10, 2026
5f813e8
Adapt to Hex1b 0.150.0 and rebased main
mitchdenny May 10, 2026
e8201bc
WithTerminal Phase 18: package Aspire.TerminalHost as per-RID NuGet p…
mitchdenny May 11, 2026
354ebd7
WithTerminal Phase 19: integrate Aspire.TerminalHost into the polyglo…
mitchdenny May 11, 2026
7ac56bf
docs/specs/with-terminal: fix MD040 fenced-code-language
mitchdenny May 11, 2026
362acdb
WithTerminal Phase 20: fix CI failures from Phase 18/19
mitchdenny May 11, 2026
b97b221
WithTerminal Phase 22: review feedback cleanup pass 1
mitchdenny May 11, 2026
ceaef89
WithTerminal Phase 22: review feedback documentation pass
mitchdenny May 11, 2026
053e54e
WithTerminal Phase 23: aspire terminal ps command + per-replica metadata
mitchdenny May 11, 2026
f69afc1
Per-replica TerminalHost: one host process per parent replica
mitchdenny May 11, 2026
6ad0df0
chore: whitespace nudge to retrigger CI
mitchdenny May 11, 2026
6116143
Defer per-replica TerminalHost creation to BeforeStartEvent
mitchdenny May 11, 2026
670af13
Add ATS export for WithTerminal via parameterless dispatcher overload
mitchdenny May 11, 2026
d50d829
Fall back to ASPIRE_DASHBOARD_PATH when terminal host path is unset i…
mitchdenny May 11, 2026
07cd081
Drop TerminalSpec.Enabled and Windows-only gate
May 13, 2026
8445464
playground: add Unix bash shell + auto-wire TerminalHost project ref
mitchdenny May 16, 2026
5cf3129
Post-rebase fixups + bump Hex1b 0.154 → 0.161
mitchdenny May 30, 2026
48572e1
Dashboard: lift terminal chrome into ConsoleLogs toolbar
mitchdenny May 30, 2026
d4e74a7
Dashboard: hide log-only toolbar items in terminal mode
mitchdenny May 30, 2026
2649201
Dashboard: consolidate terminal primary badge + take-control button
mitchdenny May 30, 2026
51e07d7
Dashboard terminal: diagnostics + pooled larger outbound buffer
mitchdenny May 30, 2026
3d3adf2
Dashboard terminal: faster UDS drain to avoid Hex1b slow-peer eviction
mitchdenny May 30, 2026
bb70bbb
Dashboard terminal: auto-take primary when adjusting font/size
mitchdenny May 30, 2026
2122e28
Dashboard terminal: apply local size before auto-promoting
mitchdenny May 30, 2026
0904bdd
Dashboard terminal: theme-aware stage background + Aspire-purple glow
mitchdenny May 30, 2026
9cd66d1
Dashboard terminal: bundle Cascadia Mono NF for Powerline/Nerd glyphs
mitchdenny May 30, 2026
7ee41d1
Dashboard terminal: preload Cascadia Mono NF before xterm construction
mitchdenny May 30, 2026
7192073
Use term.reset() instead of term.clear() on WS reconnect
Copilot May 30, 2026
8adac5c
Bump Hex1b 0.161 → 0.163 + post-rebase fixups
mitchdenny Jun 2, 2026
4cb1cb7
Validate Origin on terminal WebSocket upgrade (CSWSH defense)
mitchdenny Jun 3, 2026
688e670
Restrict terminal-host control socket to 0600
mitchdenny Jun 3, 2026
3f1dc02
Clean up terminal-host temp directory on ApplicationStopped
mitchdenny Jun 3, 2026
f692878
Pre-delete and post-delete producer/consumer UDS files in TerminalRep…
mitchdenny Jun 3, 2026
0a09a06
WithTerminal: move per-replica UDS sockets and metadata sidecar under…
mitchdenny Jun 3, 2026
e9147ed
WithTerminal: rename control sock purpose 'control' -> 'ctrl'
mitchdenny Jun 3, 2026
9fdd3a6
WithTerminal: assert manifest exclusion uses Ignore singleton
mitchdenny Jun 3, 2026
7b4926c
WithTerminal: add ShowTerminalHosts option (default false)
mitchdenny Jun 3, 2026
4c71b99
WithTerminal: drop StderrLoggerProvider, wire OTel exporters
mitchdenny Jun 3, 2026
c668f14
Drop bogus ReplicasStarted counter, add ConsumerConnections
mitchdenny Jun 3, 2026
4b31190
Add ILogger<T> + OTel metrics for terminal host lifecycle events
mitchdenny Jun 3, 2026
0e4b029
Remove dashboard diagnostics instrumentation from Program.cs
mitchdenny Jun 3, 2026
1f7016f
Add startup OTLP env diagnostic to terminal host stderr
mitchdenny Jun 3, 2026
c5a2caa
Distribute Aspire.TerminalHost via CLI bundle only
mitchdenny Jun 3, 2026
64968f9
Adopt DCP socketMode=connect, IHost-based terminal host, debug logging
mitchdenny Jun 4, 2026
1450045
Review batch A: capability gates, doc fixes, dead code removal
mitchdenny Jun 4, 2026
a8b3528
TerminalHostArgs: adopt System.CommandLine for parsing (#6)
mitchdenny Jun 4, 2026
a35dd37
Review batch B: correctness/DoS/UX fixes
mitchdenny Jun 4, 2026
ff87bfb
Review batch C: test cleanup (#21-#24)
mitchdenny Jun 4, 2026
0e304b5
WithTerminal: rename ShowTerminalHosts -> ShowTerminalHost
mitchdenny Jun 4, 2026
d08f9fe
Sdk.in.targets: revert gratuitous comment reflow
mitchdenny Jun 4, 2026
ef7b636
Backchannel: consolidate Terminals_PsV1 into Terminals_V1
mitchdenny Jun 4, 2026
d6c26ba
Comments: strip session-narration 'Phase N' references
mitchdenny Jun 4, 2026
275fcbd
Terminal commands: adopt CommonCommandServices ctor
mitchdenny Jun 4, 2026
35cae1d
Accept codegen snapshots for WithTerminal()
mitchdenny Jun 4, 2026
aa1f311
Address @adamint review feedback on PR #17866
mitchdenny Jun 5, 2026
2099622
Dashboard: terminal UI polish
mitchdenny Jun 5, 2026
dbc5b03
Review #17: TerminalHost failure diagnostics + comment cleanup
Jun 7, 2026
27ade1c
CLI: always inject ASPIRE_TERMINAL_HOST_PATH from bundle
mitchdenny Jun 7, 2026
6ea2944
TerminalHost: tolerate transient EAGAIN/EINTR in control accept loop
mitchdenny Jun 7, 2026
41d9fe5
CLI: prefer repo-local aspire-managed for terminal host in DEBUG repo…
mitchdenny Jun 9, 2026
ece2a6a
CLI: inject terminal host env vars for guest AppHosts + TerminalsJs s…
mitchdenny Jun 9, 2026
f534635
Merge remote-tracking branch 'origin/main' into feature/with-terminal
mitchdenny Jun 9, 2026
4be4daa
Mark WithTerminal experimental + gate terminal CLI commands behind fe…
mitchdenny Jun 10, 2026
a9ca800
Potential fix for pull request finding
mitchdenny Jun 10, 2026
c9984d9
Potential fix for pull request finding
mitchdenny Jun 10, 2026
9a5e42a
Potential fix for pull request finding
mitchdenny Jun 10, 2026
7505af8
Add THIRD-PARTY-NOTICES entries for Cascadia Code and xterm.js
mitchdenny Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Aspire.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
<Project Path="src/Aspire.Hosting.Blazor/Aspire.Hosting.Blazor.csproj" />
<Project Path="src/Aspire.Hosting.Yarp/Aspire.Hosting.Yarp.csproj" />
<Project Path="src/Aspire.Hosting/Aspire.Hosting.csproj" />
<Project Path="src/Aspire.TerminalHost/Aspire.TerminalHost.csproj" />
<Project Path="src/Aspire.TypeSystem/Aspire.TypeSystem.csproj" />
<Project Path="src/Aspire.ProjectTemplates/Aspire.ProjectTemplates.csproj" />
</Folder>
Expand Down Expand Up @@ -383,6 +384,10 @@
<Project Path="playground/Stress/Stress.Empty/Stress.Empty.csproj" />
<Project Path="playground/Stress/Stress.TelemetryService/Stress.TelemetryService.csproj" />
</Folder>
<Folder Name="/playground/Terminals/">
<Project Path="playground/Terminals/Terminals.AppHost/Terminals.AppHost.csproj" />
<Project Path="playground/Terminals/Terminals.Repl/Terminals.Repl.csproj" />
</Folder>
<Folder Name="/playground/Testing/">
<Project Path="playground/Testing/Testing.Tests/Testing.Tests.csproj" />
</Folder>
Expand Down Expand Up @@ -536,6 +541,7 @@
<Project Path="tests/Aspire.Hosting.SqlServer.Tests/Aspire.Hosting.SqlServer.Tests.csproj" />
<Project Path="tests/Aspire.Hosting.Testing.Tests/Aspire.Hosting.Testing.Tests.csproj" />
<Project Path="tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj" />
<Project Path="tests/Aspire.TerminalHost.Tests/Aspire.TerminalHost.Tests.csproj" />
<Project Path="tests/Aspire.Hosting.Valkey.Tests/Aspire.Hosting.Valkey.Tests.csproj" />
<Project Path="tests/Aspire.Hosting.Blazor.Tests/Aspire.Hosting.Blazor.Tests.csproj" />
<Project Path="tests/Aspire.Hosting.Yarp.Tests/Aspire.Hosting.Yarp.Tests.csproj" />
Expand Down
2 changes: 2 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<SkipAspireWorkloadManifest>true</SkipAspireWorkloadManifest>

<DashboardPublishedArtifactsOutputDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'DashboardArtifacts', '$(Configuration)'))</DashboardPublishedArtifactsOutputDir>
<TerminalHostPublishedArtifactsOutputDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'TerminalHostArtifacts', '$(Configuration)'))</TerminalHostPublishedArtifactsOutputDir>
<WorkloadsPackageSource>$(ArtifactsShippingPackagesDir)</WorkloadsPackageSource>

<!-- Issue: https://github.com/microsoft/aspire/issues/8488 -->
Expand All @@ -55,6 +56,7 @@
<!-- TODO: Need to figure out we can automatically detect target framework here. This property
is specified to support dashboard path metadata generation on the inner loop. -->
<AspireDashboardDir>$(MSBuildThisFileDirectory)/artifacts/bin/Aspire.Dashboard/$(Configuration)/net8.0/</AspireDashboardDir>
<AspireTerminalHostDir>$(MSBuildThisFileDirectory)/artifacts/bin/Aspire.TerminalHost/$(Configuration)/net8.0/</AspireTerminalHostDir>
</PropertyGroup>

</Project>
6 changes: 3 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@
<PackageVersion Include="Grpc.AspNetCore" Version="2.80.0" />
<PackageVersion Include="Grpc.Net.ClientFactory" Version="2.80.0" />
<PackageVersion Include="Grpc.Tools" Version="2.80.0" />
<PackageVersion Include="Hex1b" Version="0.154.0" />
<PackageVersion Include="Hex1b.McpServer" Version="0.154.0" />
<PackageVersion Include="Hex1b.Tool" Version="0.154.0" />
<PackageVersion Include="Hex1b" Version="0.163.0" />
<PackageVersion Include="Hex1b.McpServer" Version="0.163.0" />
<PackageVersion Include="Hex1b.Tool" Version="0.163.0" />
<PackageVersion Include="Humanizer.Core" Version="3.0.10" />
<PackageVersion Include="KubernetesClient" Version="19.0.2" />
<PackageVersion Include="JsonPatch.Net" Version="5.0.2" />
Expand Down
124 changes: 124 additions & 0 deletions THIRD-PARTY-NOTICES.TXT
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,127 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


License notice for Cascadia Code
----------------------------------------------------------------------------------------------

Copyright (c) 2019 - Present, Microsoft Corporation,
with Reserved Font Name Cascadia Code.

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL

-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.


License notice for xterm.js
----------------------------------------------------------------------------------------------

Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com)
Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
166 changes: 166 additions & 0 deletions docs/specs/with-terminal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# `WithTerminal()` — Aspire interactive terminal architecture

**Status:** Implemented for Aspire 13.4 (Windows executables).
**Issue:** [microsoft/aspire#16317](https://github.com/microsoft/aspire/issues/16317)
**DCP integration:** [microsoft/dcp#133](https://github.com/microsoft/dcp/pull/133)

## Goal

Let an Aspire AppHost author opt any executable or container resource into
interactive terminal access:

```csharp
builder.AddProject<Projects.MyAgent>("agent")
.WithReplicas(2)
.WithTerminal();
```

The dashboard then renders an xterm.js terminal per replica, and the CLI
exposes the same session as `aspire terminal agent --replica 0`.

## Process topology

```text
┌────────────────────────────┐
│ AppHost (dotnet run) │
│ - Aspire.Hosting │
│ - DCP control plane │
│ - per-replica: │
│ Aspire.TerminalHost │ (1 process per replica)
└─────────────┬──────────────┘
Comment thread
mitchdenny marked this conversation as resolved.
│ spawn
┌──────────────────────┐ ┌───────────────────────┐ ┌────────────────────────┐
│ DCP-launched │ PTY │ TerminalHost │ HMP v1 UDS │ Consumers │
│ replica process │ ─────────────▶ │ (Hex1b HMP v1 broker) │ ─────────────▶ │ - Dashboard │
│ (executable, repl…) │ ◀───── stdin ─ │ │ ◀───── input ─ │ /api/terminal proxy │
└──────────────────────┘ └───────────────────────┘ │ - aspire CLI │
└────────────────────────┘
```

Three actors and three socket roles:

| Actor | Socket | Direction | Lifetime |
|----------------|---------------------|----------------------------------|----------|
| **DCP** | `producerUdsPath` | DCP → host (PTY bytes + control) | Per replica |
| **TerminalHost** | `consumerUdsPath` | host → consumers (broadcast) | Per replica |
| **TerminalHost** | `controlUdsPath` | AppHost → host (lifecycle, stats)| Per replica |

The producer/consumer split lets multiple consumers (dashboard + multiple CLI
sessions) attach simultaneously without coupling DCP to consumer counts.

## Wire protocol

We do **not** define a custom protocol. The terminal traffic uses
[Hex1b](https://github.com/dotnet/hex1b)'s `HMP v1` (Hex Multiplex Protocol,
version 1), which already handles:

- VT byte streaming with backpressure
- Resize requests in both directions
- Hello/StateSync replay so a late-attaching consumer sees the current
scrollback
- Connection lifecycle (close, disconnect, reconnect)
- Authenticated stream factory hooks (we only use Unix-socket transport
today)

The `Hmp1WorkloadAdapter` is what the AppHost-side terminal host uses to
multiplex DCP's PTY traffic to the consumer-facing listener; the
`Hmp1PresentationAdapter` is what consumers (Dashboard WebSocket proxy and
the CLI) use to attach.

## Property contract (gRPC `ResourceService` snapshots)

When `WithTerminal()` is applied to a resource, every replica snapshot
emitted by the dashboard service carries four properties:

| Key | Sensitivity | Meaning |
|---------------------------|-----------------|----------------------------------------------|
| `terminal.enabled` | non-sensitive | Marker. `"true"` when the replica has a PTY. |
| `terminal.replicaIndex` | non-sensitive | 0-based stable index from `DcpInstancesAnnotation`. |
| `terminal.replicaCount` | non-sensitive | Total replicas for the parent resource. |
| `terminal.consumerUdsPath`| **sensitive** | The local UDS that consumers connect to. |

The consumer UDS path is marked `IsSensitive=true` so the dashboard UI masks
the value in the property list. The path still rides the gRPC stream because
the dashboard's WebSocket proxy needs it server-side to resolve
`?resource=&replica=` query parameters into a real socket; the path is never
echoed back to the browser.

## Dashboard `/api/terminal` WebSocket endpoint

Authenticated (`RequireAuthorization(FrontendAuthorizationDefaults.PolicyName)`)
endpoint at `/api/terminal?resource=<displayName>&replica=<index>`.

`TerminalWebSocketProxy` resolves the connection entirely server-side:

1. `ITerminalConnectionResolver.ConnectAsync(resourceName, replicaIndex, ct)`
walks `IDashboardClient.GetResources()`, matches by `DisplayName` +
`TryGetTerminalReplicaInfo`, and connects via
`Hmp1Transports.ConnectUnixSocket(consumerUdsPath, ct)`.
2. The proxy wraps the resulting stream in `Hmp1WorkloadAdapter` and runs
two pumps:
- **Inbound (browser → producer):** binary frames are forwarded as HMP v1
`Input` (keystrokes); text frames are parsed as JSON resize control
messages (`{"type":"resize","cols":N,"rows":N}`).
- **Outbound (producer → browser):** VT bytes from the producer become
binary WebSocket frames; resize hints from the producer become JSON
text frames.
3. Frame type — not content — distinguishes keystroke from control. This
keeps the proxy's parser cheap and avoids ambiguity around binary input
that happens to look like JSON.
4. Multi-fragment WS reads are reassembled in `ReassembledFrame` using
`ArrayPool<byte>`.

The browser never sees `consumerUdsPath` and cannot induce the dashboard
to connect to an arbitrary local socket — it can only ask for
`(resource, replica)` pairs that are present in the resource snapshot
stream.

## CLI

`aspire terminal <resource> [--replica N]` (`Aspire.Cli/Commands/TerminalCommand.cs`)
opens its own `Hmp1PresentationAdapter` against the consumer UDS path
returned by `IBackchannel.GetTerminalInfoAsync(resource, replica)` and
renders frames into the host terminal via Hex1b's `Hex1bTerminal`. When the
resource has more than one replica and the CLI is interactive, it prompts
for a selection; in non-interactive mode the `--replica` flag is required.

## DCP integration

For each replica of a `WithTerminal()` resource, DCP allocates a pseudo-terminal
when the executable (or container) spec carries a populated `terminal` block:

```json
{
"terminal": {
"udsPath": "/run/user/1000/aspire/trmnl/<run-id>/<resource>-<idx>/producer.sock",
"socketMode": "connect",
"cols": 120,
"rows": 30
}
}
```

`socketMode: "connect"` tells DCP to dial the named UDS (the TerminalHost
process owns the listener). The dimensions are the initial PTY size; both
sides exchange resize frames over HMP afterwards.

Desktop PTY support is implemented across all three platforms (Unix98 `/dev/ptmx` on Linux and macOS; ConPTY on Windows). Container PTYs are tracked
as a Phase 3 follow-up on the parent issue.

## Files of interest

| Concern | File |
|--------------------------------------|---------------------------------------------------------------------|
| Public API entry point | `src/Aspire.Hosting/TerminalResourceBuilderExtensions.cs` |
| Per-resource hidden host resource | `src/Aspire.Hosting/ApplicationModel/TerminalHostResource.cs` |
| DCP wire-up | `src/Aspire.Hosting/Dcp/ExecutableCreator.cs` |
| Backchannel `GetTerminalInfoAsync` | `src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs` |
| Snapshot stamping | `src/Aspire.Hosting/Dashboard/DashboardServiceData.cs` |
| TerminalHost process | `src/Aspire.TerminalHost/` |
| CLI command | `src/Aspire.Cli/Commands/TerminalCommand.cs` |
| Dashboard WebSocket proxy | `src/Aspire.Dashboard/Terminal/TerminalWebSocketProxy.cs` |
| Dashboard resolver | `src/Aspire.Dashboard/Terminal/DefaultTerminalConnectionResolver.cs`|
| `TerminalView` (xterm.js host) | `src/Aspire.Dashboard/Components/Controls/TerminalView.razor.*` |
| Property keys | `src/Shared/Model/KnownProperties.cs` (`Terminal.*`) |
| Playground sample | `playground/Terminals/Terminals.AppHost/AppHost.cs` |
Loading
Loading