diff --git a/.github/workflows/installer-verify.yml b/.github/workflows/installer-verify.yml new file mode 100644 index 000000000..a9910cd52 --- /dev/null +++ b/.github/workflows/installer-verify.yml @@ -0,0 +1,45 @@ +name: Verify installers + +on: + pull_request: + paths: + - "install*.ps1" + - "install*.sh" + - "uninstall*.ps1" + - "uninstall*.sh" + - "scripts/verify-installers.ps1" + - "src/Ai.Tlbx.MidTerm.Common/Identity/**" + - "src/Ai.Tlbx.MidTerm/Settings/**" + - "src/Ai.Tlbx.MidTerm/Startup/**" + - "src/Ai.Tlbx.MidTerm/Services/Certificates/**" + - "src/Ai.Tlbx.MidTerm/Services/Updates/**" + push: + branches: [main, dev] + paths: + - "install*.ps1" + - "install*.sh" + - "uninstall*.ps1" + - "uninstall*.sh" + - "scripts/verify-installers.ps1" + - "src/Ai.Tlbx.MidTerm.Common/Identity/**" + - "src/Ai.Tlbx.MidTerm/Settings/**" + - "src/Ai.Tlbx.MidTerm/Startup/**" + - "src/Ai.Tlbx.MidTerm/Services/Certificates/**" + - "src/Ai.Tlbx.MidTerm/Services/Updates/**" + +permissions: + contents: read + +jobs: + verify: + strategy: + matrix: + os: [ubuntu-24.04, windows-2025] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + persist-credentials: false + - name: Verify installer contracts + shell: pwsh + run: ./scripts/verify-installers.ps1 diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index e819e5aca..5dd26bd35 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -32,6 +32,9 @@ jobs: source: ./docs/site destination: ./_site + - name: Make Pages artifact writable + run: sudo chown -R "$(id -u):$(id -g)" ./_site + - name: Publish canonical signed-release installers run: | cp ./install.sh ./_site/install.sh @@ -44,9 +47,6 @@ jobs: test -f ./_site/uninstall.sh test -f ./_site/uninstall.ps1 - - name: Make Pages artifact writable - run: sudo chown -R "$(id -u):$(id -g)" ./_site - - name: Normalize and verify duplicate Pages search metadata shell: python run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b2b66f818..de612671d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -642,9 +642,14 @@ jobs: Copy-Item "src/Ai.Tlbx.MidTerm/bin/Release/net10.0/$rid/publish/mt.exe" "staging/$rid/" Copy-Item "src/Ai.Tlbx.MidTerm.TmuxShim/bin/Release/net10.0/$rid/publish/mttmux.exe" "staging/$rid/" Copy-Item src/version.json "staging/$rid/" + Copy-Item "src/Ai.Tlbx.MidTerm/src/static/THIRD-PARTY-LICENSES.txt" "staging/$rid/" $mthostPath = "src/Ai.Tlbx.MidTerm.TtyHost/bin/Release/net10.0/$rid/publish/mthost.exe" if (Test-Path $mthostPath) { Copy-Item $mthostPath "staging/$rid/" + ./scripts/copy-windows-conpty-runtime.ps1 ` + -SourceDir (Split-Path -Parent $mthostPath) ` + -DestinationDir "staging/$rid" ` + -Rid $rid } else { Write-Host "mthost.exe not found for $rid; continuing with web-only artifact set" } @@ -654,13 +659,12 @@ jobs: shell: pwsh run: | $rid = '${{ matrix.rid }}' - $files = @("staging/$rid/mt.exe", "staging/$rid/mtagenthost.exe", "staging/$rid/mttmux.exe") - if (Test-Path "staging/$rid/mthost.exe") { - $files += "staging/$rid/mthost.exe" - } - $hashes = Get-ChildItem $files | ForEach-Object { + $root = (Resolve-Path "staging/$rid").Path + $files = Get-ChildItem $root -Recurse -File | Where-Object Name -NotIn @("version.json", "SHA256SUMS.txt") + $hashes = $files | ForEach-Object { $hash = (Get-FileHash $_ -Algorithm SHA256).Hash.ToLower() - "$hash $($_.Name)" + $relativePath = [IO.Path]::GetRelativePath($root, $_.FullName).Replace('\', '/') + "$hash $relativePath" } $hashes | Set-Content "staging/$rid/SHA256SUMS.txt" Write-Host "=== $rid ===" @@ -743,9 +747,14 @@ jobs: Copy-Item "src/Ai.Tlbx.MidTerm/bin/Release/net10.0/$rid/publish/mt.exe" "staging/$rid/" Copy-Item "src/Ai.Tlbx.MidTerm.TmuxShim/bin/Release/net10.0/$rid/publish/mttmux.exe" "staging/$rid/" Copy-Item src/version.json "staging/$rid/" + Copy-Item "src/Ai.Tlbx.MidTerm/src/static/THIRD-PARTY-LICENSES.txt" "staging/$rid/" $mthostPath = "src/Ai.Tlbx.MidTerm.TtyHost/bin/Release/net10.0/$rid/publish/mthost.exe" if (Test-Path $mthostPath) { Copy-Item $mthostPath "staging/$rid/" + ./scripts/copy-windows-conpty-runtime.ps1 ` + -SourceDir (Split-Path -Parent $mthostPath) ` + -DestinationDir "staging/$rid" ` + -Rid $rid } else { Write-Host "mthost.exe not found for $rid; continuing with web-only artifact set" } @@ -789,13 +798,12 @@ jobs: shell: pwsh run: | $rid = '${{ matrix.rid }}' - $files = @("staging/$rid/mt.exe", "staging/$rid/mtagenthost.exe", "staging/$rid/mttmux.exe") - if (Test-Path "staging/$rid/mthost.exe") { - $files += "staging/$rid/mthost.exe" - } - $hashes = Get-ChildItem $files | ForEach-Object { + $root = (Resolve-Path "staging/$rid").Path + $files = Get-ChildItem $root -Recurse -File | Where-Object Name -NotIn @("version.json", "SHA256SUMS.txt") + $hashes = $files | ForEach-Object { $hash = (Get-FileHash $_ -Algorithm SHA256).Hash.ToLower() - "$hash $($_.Name)" + $relativePath = [IO.Path]::GetRelativePath($root, $_.FullName).Replace('\', '/') + "$hash $relativePath" } $hashes | Set-Content "staging/$rid/SHA256SUMS.txt" Write-Host "=== $rid ===" diff --git a/README.md b/README.md index 25e523e08..3fb23867b 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ curl -fsSL https://get.tlbx.ai/install.sh | bash irm https://get.tlbx.ai/install.ps1 | iex ``` -Open `https://localhost:2000`. Choose service mode for a host that should survive logouts and reboots; user mode needs no administrator access. +The installer starts tlbx, waits until its process and HTTPS health endpoint are ready, and then prints clickable localhost and available Tailscale URLs for the configured port. Choose service mode for a host that should survive logouts and reboots; user mode needs no administrator access. These commands install the current stable release. Add `--dev` on macOS/Linux or `-Dev` on Windows only when you explicitly want the prerelease channel. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a844f61bd..52ef87273 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -602,6 +602,8 @@ The root `install.ps1` and `install.sh` scripts handle: - certificate reuse plus trust flows for both newly generated and reused certificates - platform-specific install paths and service registration - channel selection and release download +- detached user-mode startup, supervised service startup, and fatal process/HTTPS readiness verification +- ready-to-open localhost plus reachable Tailscale URLs for the configured bind address and port - update logging ### Update Service diff --git a/docs/AgentControllerSessionDesign.md b/docs/AgentControllerSessionDesign.md index 76e244f5b..060b73e92 100644 --- a/docs/AgentControllerSessionDesign.md +++ b/docs/AgentControllerSessionDesign.md @@ -681,7 +681,7 @@ Status in this branch/work item: - implemented: the history pane itself is again the native local pixel scroller for the currently materialized kernel, while browse-mode retained-window growth and trims preserve the reader anchor - implemented: ordinary local pane scrolling no longer force-refreshes the already loaded history window when no window shift is needed; forced same-window refetch is reserved for urgent void-recovery cases where the viewport has lost all intersecting concrete rows - implemented: direct progress-nav scrubs now jump to a tiny centered preview window first and then hydrate into a normal browse window after drag idle, so large jumps do not try to materialize the traversed span -- implemented gap: canonical interactive request/question flows now have a dedicated frontend interview widget, but the backend model still represents them as request summaries rather than a first-class canonical `interview` item type +- implemented: canonical interactive request/question flows are first-class `interview` history items with embedded questions and answers; the frontend prefers that self-renderable payload and retains request-summary lookup only as a compatibility fallback Still mandatory after this work whenever Agent Controller Session evolves: diff --git a/docs/ai/AGENTS.md b/docs/ai/AGENTS.md index 986d14fbf..06a1c67b1 100644 --- a/docs/ai/AGENTS.md +++ b/docs/ai/AGENTS.md @@ -95,8 +95,9 @@ tlbx is a web-based terminal multiplexer. Native AOT compiled, runs on macOS/Win - `mthost` / `mthost.exe` — TTY host (spawned per terminal, all platforms) **Settings locations:** -- Service mode: `%ProgramData%\MidTerm\settings.json` (Win) or `/usr/local/etc/midterm/settings.json` (Unix) -- User mode: `~/.midterm/settings.json` +- Service mode: `%ProgramData%\tlbx\settings.json` (Win) or `/usr/local/etc/tlbx/settings.json` (Unix) +- User mode: `~/.tlbx/settings.json` +- Existing legacy installations continue to use their `MidTerm` / `.midterm` paths in place. **Platform defaults:** - Windows — ConPTY, default shell `Pwsh` diff --git a/docs/ai/CLAUDE.md b/docs/ai/CLAUDE.md index ad5865094..0808e9910 100644 --- a/docs/ai/CLAUDE.md +++ b/docs/ai/CLAUDE.md @@ -70,8 +70,9 @@ tlbx is a web-based terminal multiplexer. Native AOT compiled, runs on macOS/Win - `mthost` / `mthost.exe` — TTY host (spawned per terminal, all platforms) **Settings locations:** -- Service mode: `%ProgramData%\MidTerm\settings.json` (Win) or `/usr/local/etc/midterm/settings.json` (Unix) -- User mode: `~/.midterm/settings.json` +- Service mode: `%ProgramData%\tlbx\settings.json` (Win) or `/usr/local/etc/tlbx/settings.json` (Unix) +- User mode: `~/.tlbx/settings.json` +- Existing legacy installations continue to use their `MidTerm` / `.midterm` paths in place. **Platform defaults:** - Windows — ConPTY, default shell `Pwsh` diff --git a/docs/multi-instance-install.md b/docs/multi-instance-install.md index ffcd5f8e6..2e8229a4e 100644 --- a/docs/multi-instance-install.md +++ b/docs/multi-instance-install.md @@ -39,9 +39,9 @@ Use the normal installer when: - the service should stay at the default identity and port - you are upgrading an existing normal installation -Do not mix installer families for the same instance. A normal `MidTerm` service -should be managed by the normal installer. `MidTerm-`, -`midterm-`, and `ai.tlbx.midterm.` services should be managed by +Do not mix installer families for the same instance. A normal `tlbx` service +should be managed by the normal installer. `tlbx-` and +`ai.tlbx.instance.` services should be managed by the multi-instance installer. ## Platform and architecture support @@ -95,9 +95,9 @@ For an instance named `alice`, the generated identities are: | Platform | Service identity | | --- | --- | -| Windows | `MidTerm-alice` | -| Linux | `midterm-alice` | -| macOS | `ai.tlbx.midterm.alice` | +| Windows | `tlbx-alice` | +| Linux | `tlbx-alice` | +| macOS | `ai.tlbx.instance.alice` | The settings directory contains an `instance.json` manifest with the effective name, port, bind address, service identity, install directory, settings @@ -109,19 +109,19 @@ Windows defaults: | Purpose | Default | | --- | --- | -| Settings root | `%ProgramData%\MidTerm\instances` | -| Install root | `%ProgramFiles%\MidTerm\instances` | -| Instance settings | `%ProgramData%\MidTerm\instances\` | -| Instance binaries | `%ProgramFiles%\MidTerm\instances\` | +| Settings root | `%ProgramData%\tlbx\instances` | +| Install root | `%ProgramFiles%\tlbx\instances` | +| Instance settings | `%ProgramData%\tlbx\instances\` | +| Instance binaries | `%ProgramFiles%\tlbx\instances\` | macOS and Linux defaults: | Purpose | Default | | --- | --- | -| Settings root | `/usr/local/etc/midterm-instances` | -| Install root | `/usr/local/lib/midterm/instances` | -| Instance settings | `/usr/local/etc/midterm-instances/` | -| Instance binaries | `/usr/local/lib/midterm/instances/` | +| Settings root | `/usr/local/etc/tlbx-instances` | +| Install root | `/usr/local/lib/tlbx/instances` | +| Instance settings | `/usr/local/etc/tlbx-instances/` | +| Instance binaries | `/usr/local/lib/tlbx/instances/` | These roots are intentionally separate from the normal installer paths. Do not point a multi-instance install at the normal service directory. @@ -149,8 +149,8 @@ Run from an elevated PowerShell session. | `-BasePort` | `int` | `2000` | `install`, `plan` | First candidate port when `-Ports` is not provided. Used ports are skipped. | | `-Ports` | `int[]` | empty | `install`, `plan`, `update`, `remove` | Explicit ports. Must contain exactly one port per resolved instance name. | | `-BindAddress` | `string` | `0.0.0.0` | `install`, `update`, `update-all`, `plan` | Address passed to `mt --bind`. Use `127.0.0.1` for local-only access. | -| `-RootDir` | `string` | `%ProgramData%\MidTerm\instances` | all | Root for instance settings and manifests. | -| `-InstallRoot` | `string` | `%ProgramFiles%\MidTerm\instances` | install/update/remove | Root for instance binaries. | +| `-RootDir` | `string` | `%ProgramData%\tlbx\instances` | all | Root for instance settings and manifests. | +| `-InstallRoot` | `string` | `%ProgramFiles%\tlbx\instances` | install/update/remove | Root for instance binaries. | | `-VersionTag` | `string` | `latest` | install/update/update-all | GitHub release tag, for example `v9.18.0-dev`. | | `-AssetPath` | `string` | empty | install/update/update-all | Local zip asset. Overrides GitHub download. | | `-PasswordHash` | `string` | empty | install | Precomputed PBKDF2 password hash. Preferred for automated agents. | @@ -182,8 +182,8 @@ sudo ./install-multi.sh --mode remove --names alice | `--base-port` | `N` | `2000` | `install`, `plan` | First candidate port when `--ports` is not provided. Used ports are skipped when `ss` or `lsof` is available. | | `--ports` | `p1,p2,p3` | empty | `install`, `plan`, `update`, `remove` | Explicit ports. Must contain exactly one port per resolved instance name. | | `--bind` | address | `0.0.0.0` | `install`, `update`, `update-all`, `plan` | Address passed to `mt --bind`. Use `127.0.0.1` for local-only access. | -| `--root-dir` | path | `/usr/local/etc/midterm-instances` | all | Root for instance settings and manifests. | -| `--install-root` | path | `/usr/local/lib/midterm/instances` | install/update/remove | Root for instance binaries. | +| `--root-dir` | path | `/usr/local/etc/tlbx-instances` | all | Root for instance settings and manifests. | +| `--install-root` | path | `/usr/local/lib/tlbx/instances` | install/update/remove | Root for instance binaries. | | `--version-tag` | tag | `latest` | install/update/update-all | GitHub release tag, for example `v9.18.0-dev`. | | `--asset-path` | path | empty | install/update/update-all | Local tar.gz asset. Overrides GitHub download. | | `--password-hash` | hash | empty | install | Precomputed PBKDF2 password hash. Preferred for automated agents. | @@ -200,14 +200,16 @@ valid for manual diagnostics. | Runtime flag | Environment variable | Meaning | | --- | --- | --- | -| `--port ` | `MIDTERM_PORT` | HTTPS listener port. | -| `--bind
` | `MIDTERM_BIND` | Listener bind address. | -| `--settings-dir ` | `MIDTERM_SETTINGS_DIR` | Settings, secrets, certs, logs, sessions, update state. | -| `--service-mode` | `MIDTERM_SERVICE_MODE=true` | Force service-mode paths and secret storage behavior. | -| `--user-mode` | `MIDTERM_SERVICE_MODE=false` | Force user-mode behavior. Do not use for installed multi-instance services. | -| `--service-name ` | `MIDTERM_SERVICE_NAME` | Windows service identity used by updates and restarts. | -| `--launchd-label