Skip to content

Stabilize API-backed TUI connection#236

Merged
TraderSamwise merged 4 commits into
masterfrom
chore/next-20260623-tui-connection-adapter-11
Jun 23, 2026
Merged

Stabilize API-backed TUI connection#236
TraderSamwise merged 4 commits into
masterfrom
chore/next-20260623-tui-connection-adapter-11

Conversation

@TraderSamwise

@TraderSamwise TraderSamwise commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • harden project-service readiness and dashboard request recovery so transient local API delays do not restart services or apply contradictory empty states
  • cache desktop-state with stale-while-revalidate and remove no-op metadata poller churn that was pinning project-service event loops
  • make tmux runtime sessions use the canonical project ID, with automatic legacy session rename repair so daemon/service/TUI agree on the same project

Verification

  • yarn typecheck
  • yarn lint
  • yarn vitest
  • yarn build
  • git diff --check
  • AIMUX_RELEASE_VERSION=local-tui-api-stability-20260623g yarn release:asset
  • scripts/install.sh release/aimux-darwin-arm64.tar.gz
  • live project-service health checks for aimux/tealstreet-next/premys under 1ms after install

Summary by CodeRabbit

  • Bug Fixes

    • Prevented redundant status, plan progress, and related event/log emissions on unchanged polling.
    • Avoided deriving history/events for non-live sessions and added protection against contradictory/empty desktop-state snapshots.
    • Strengthened project-service and dashboard endpoint liveness checks to avoid unnecessary retries/restarts.
  • New Features

    • Added timeout-aware daemon and project-service startup with readiness/adoption locking.
    • Improved desktop-state caching with longer TTL and background refresh on updates/alerts.
  • Improvements

    • Introduced tri-state endpoint health caching and stabilized tmux project session naming with automatic legacy repairs.
  • Tests

    • Expanded polling deduplication, readiness gating, liveness verification, desktop-state refresh, and legacy tmux repair coverage.

@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jun 23, 2026 2:37pm

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd8f4ad6-c55f-4f80-a76f-c1ea2a326b1c

📥 Commits

Reviewing files that changed from the base of the PR and between a02a4ac and 06a6f58.

📒 Files selected for processing (2)
  • src/tmux/runtime-manager.test.ts
  • src/tmux/runtime-manager.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tmux/runtime-manager.ts
  • src/tmux/runtime-manager.test.ts

📝 Walkthrough

Walkthrough

The PR hardens aimux service reliability across multiple subsystems: daemon startup gains a filesystem lock and readiness-verified spawn; dashboard-control endpoint health moves from boolean to a "current" | "stale" | "unknown" tristate; metadata-server desktop-state caching becomes timer-based; builtin metadata watchers deduplicate progress/status and scope history to live topology; dashboard-model guards against contradictory empty desktop-state from the project service; tmux session naming switches to project-id keys with legacy session migration; and inline @aimux-meta fetching in listManagedWindows eliminates per-window round-trips.

Changes

Tmux Session Identity and Inline Metadata

Layer / File(s) Summary
Project-id session naming and legacy migration
src/tmux/runtime-manager.ts, src/tmux/runtime-manager.test.ts, src/project-scanner.test.ts
getProjectSession derives sessionName as ${prefix}-${projectId} via getProjectIdFor; getLegacyProjectSessionName preserves the old SHA1+slug format; ensureProjectSession renames legacy sessions before setup. Tests assert the new exact name format and add regression coverage for legacy session repair.
Inline window metadata via list-windows
src/tmux/runtime-manager.ts, src/tmux/runtime-manager.test.ts, src/tmux/doctor.test.ts
listManagedWindows appends #{@aimux-meta} to the list-windows format string and parses it inline with try/catch, eliminating the prior per-window getWindowMetadata call. Tests replace separate show-window-options mocks with startsWith-matched rows that embed JSON metadata, and doctor test updated for new row shape.

Service Reliability and Runtime State Hardening

Layer / File(s) Summary
Builtin metadata watcher deduplication and topology-scoped history
src/builtin-metadata-watchers.ts, src/builtin-metadata-watchers.test.ts
createBuiltinMetadataWatchers adds lastProgressBySession deduplication for plan progress, early-continue dedup for status headlines, and switches history session enumeration to api.sessions.list(). Tests verify no re-emission on repeated polling and that out-of-topology sessions produce no history output.
Timer-based desktop-state cache refresh in MetadataServer
src/metadata-server.ts, src/metadata-server.test.ts
DESKTOP_STATE_CACHE_TTL_MS raised to 10_000 ms; MetadataServer gains desktopStateCacheDirty, desktopStateRefreshTimer, and desktopStateRefreshing; alert and notifyProjectChanged call scheduleDesktopStateRefresh instead of synchronous invalidation; stop() clears the timer. Tests renamed and timing assertions adjusted for the deferred refresh model.
managedWindows plumbing and contradictory-empty desktop-state guard
src/multiplexer/dashboard-model.ts, src/multiplexer/dashboard-model-service.test.ts
DashboardStateSnapshotOptions gains optional managedWindows; computeDashboardSessions, computeDashboardServices, and buildDesktopStateSnapshot thread the list through with host fallback. isContradictoryEmptyDesktopState and hasLiveTmuxAgentWindow helpers added; refreshDashboardModelFromService fails via failDashboardServiceRefresh when desktop-state is empty but tmux has live agents. Tests add tmux runtime manager mock and a new contradictory-empty rejection case.
Endpoint health tristate and GET/POST divergence
src/multiplexer/dashboard-control.ts, src/multiplexer/dashboard-control.test.ts
ProjectServiceEndpointState ("current" | "stale" | "unknown") replaces the boolean health flag; PROJECT_SERVICE_ENDPOINT_HEALTH_CACHE_MS set to 30_000 ms; POST requests run through endpointStateForRequest with stale-restart-retry logic; GET routes bypass health preflight entirely. healthyServiceResponse helper gains a projectRoot parameter; tests remove /health preflight expectations from GET paths and add mutating-request coverage.
Daemon startup lock and project-service readiness pipeline
src/daemon.ts, src/daemon.test.ts
daemon.ts adds a filesystem lock to serialize daemon startup, probeDefaultDaemon for adoption, waitForProjectServiceReady (polls metadata endpoint then validates PID and /health+manifest), and spawnReadyProjectService (wraps spawn with readiness gating and cleanup on failure). ensureDaemonRunning gains probe-first then lock-then-spawn flow. Tests add pid-scoped health helpers and spawn mock writes metadata-api.json.
Session-launch startup reorder and context-watcher flag
src/multiplexer/session-launch.ts, src/multiplexer/session-launch.test.ts, src/main.ts
runProjectService moves reconcileLaunchableTopology and writeInstructionFiles before refreshDesktopStateSnapshot and startProjectServices, and removes a later duplicate snapshot refresh. __project-service-internal in main.ts constructs Multiplexer with contextWatcherEnabled: false. Tests assert the new invocation order.

Sequence Diagram(s)

sequenceDiagram
  participant caller as ensureProjectService
  participant sRPS as spawnReadyProjectService
  participant wPSR as waitForProjectServiceReady
  participant fs as metadata-api.json
  participant health as ProjectService /health

  caller->>sRPS: request new project-service spawn
  sRPS->>sRPS: spawn child process (pid=N)
  loop every PROJECT_SERVICE_READY_POLL_MS
    wPSR->>fs: read metadata endpoint pid
    fs-->>wPSR: pid or absent
  end
  wPSR->>health: GET /health (with PROJECT_SERVICE_HEALTH_TIMEOUT_MS)
  health-->>wPSR: { pid, manifest, projectStateDir }
  wPSR->>wPSR: validate pid match + manifest match
  alt readiness confirmed
    sRPS-->>caller: ProjectServiceState
  else timeout or validation failure
    sRPS->>sRPS: terminate child, remove stale state
    sRPS-->>caller: throw error
  end
Loading
sequenceDiagram
  participant client as Dashboard Client
  participant dc as dashboard-control requestProjectService
  participant esfr as endpointStateForRequest
  participant emcps as endpointMatchesCurrentProjectService
  participant cp as ensureDashboardControlPlane

  alt POST request
    client->>dc: POST /some-route
    dc->>esfr: check endpoint state (30s cache)
    esfr->>emcps: GET /health
    emcps-->>esfr: "current" | "stale" | "unknown"
    alt stale + within deadline
      esfr-->>dc: "stale"
      dc->>cp: restart project service
      dc->>dc: retry POST
    else current
      esfr-->>dc: "current"
      dc->>dc: execute POST
      dc->>dc: markProjectServiceEndpointCurrent
    end
  else GET request
    client->>dc: GET /some-route
    dc->>dc: direct route, no health preflight
    dc->>dc: markProjectServiceEndpointCurrent on 2xx
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • TraderSamwise/aimux#127: Both PRs touch the session-launch/topology restore startup flow (e.g., src/multiplexer/session-launch.ts changes around reconcileLaunchableTopology/restore-related reconciliation), so the main PR's session launch ordering and topology prep changes are directly related to the retrieved PR's restore invariant reconciliation work.
  • TraderSamwise/aimux#215: Both PRs modify src/daemon.ts and its project-service lifecycle to verify identity/manifest (serviceInfo/PID/root/id) and trigger stop/replace + readiness/recovery logic on mismatches, which is a direct code-level overlap.
  • TraderSamwise/aimux#221: Both PRs modify src/multiplexer/dashboard-control.ts around requestProjectService endpoint health verification/caching (main PR's tri-state endpointStateForRequest/restart-retry logic vs. retrieved PR's cached "endpoint is current for this request" handling and cache invalidation), so the changes are directly related.

Poem

🐇 The daemon now knocks before barging in,
With a lock on the door and a health-checked spin.
Stale sessions renamed, inline meta unwound,
No ghost histories logged where no session was found.
The desktop refreshes by timer, not dread—
A well-ordered rabbit hops safely ahead! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Stabilize API-backed TUI connection' directly summarizes the main objective of the changeset, which focuses on stabilizing the API-backed TUI connection by addressing transient connection issues and session consistency problems.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/next-20260623-tui-connection-adapter-11

Comment @coderabbitai help to get the list of available commands.

@TraderSamwise

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/multiplexer/dashboard-control.ts (1)

918-927: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don’t cache endpoint health after an unverified GET.

Line 926 marks any successful request as "current", but GET routes now bypass the pid/projectStateDir/manifest check. A successful GET to a stale endpoint can therefore seed dashboardProjectServiceEndpointHealth, and the next POST will trust the cache at Lines 982-984 instead of running endpointMatchesCurrentProjectService.

Suggested fix
       if (status >= 200 && status < 300 && json?.ok !== false) {
-        markProjectServiceEndpointCurrent(host, endpoint);
         return json;
       }

Also applies to: 975-986

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/multiplexer/dashboard-control.ts` around lines 918 - 927, The
markProjectServiceEndpointCurrent function is being called unconditionally after
any successful HTTP response, but GET requests bypass the
pid/projectStateDir/manifest verification checks. This allows stale endpoints to
be marked as "current" based on unverified GET responses, causing subsequent
POST requests to incorrectly trust the cached endpoint state instead of
re-verifying with endpointMatchesCurrentProjectService. Only invoke
markProjectServiceEndpointCurrent when the request has been fully verified (for
POST requests), not after all successful responses. Additionally, review the
related caching logic in the lines mentioned (975-986) to ensure similar
protection is applied where dashboardProjectServiceEndpointHealth is accessed.
🧹 Nitpick comments (1)
src/metadata-server.test.ts (1)

205-217: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use condition-based waits instead of fixed sleeps to reduce CI flake.

The 20ms/250ms sleeps can be timing-sensitive in busy environments. Prefer vi.waitFor(...) on observable state (e.g., getState call count / refreshed seq) to keep this deterministic.

Refactor sketch
-    await new Promise((resolve) => setTimeout(resolve, 20));
+    await vi.waitFor(() => expect(getState).toHaveBeenCalledTimes(2));

Also applies to: 241-241

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/metadata-server.test.ts` around lines 205 - 217, Replace the fixed
setTimeout calls (250ms and 20ms) with vi.waitFor() conditions that wait for
observable state changes instead of relying on timing. After
server.notifyChange() is called, use vi.waitFor() to wait for getState to be
called exactly 2 times before making the third fetch request. Similarly, replace
the initial 250ms sleep with vi.waitFor() waiting for getState to be called
exactly 1 time before fetching the cached state. This ensures the test waits for
actual state changes rather than arbitrary delays, making it deterministic in CI
environments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/daemon.ts`:
- Around line 229-231: The probeDefaultDaemon function calls requestJson without
specifying a timeoutMs parameter, which allows it to hang indefinitely if a
listener accepts the connection but never responds. Add a timeoutMs parameter to
the requestJson call within the probeDefaultDaemon function to ensure the health
probe times out appropriately. Also apply the same timeout fix to the additional
health probe call mentioned at lines 477-488 that has the same issue.

In `@src/metadata-server.ts`:
- Around line 1250-1257: The setTimeout callback for desktopStateRefreshTimer is
missing exception handling for the refreshDesktopStateCache() call. The current
try-finally block only ensures cleanup via the finally clause but does not catch
exceptions thrown by refreshDesktopStateCache(). Add a catch block after the try
block (before the finally) to catch and log any exceptions thrown during the
desktop state cache refresh, preventing them from escaping the timer callback
and crashing the process.

In `@src/multiplexer/dashboard-model.ts`:
- Around line 1029-1033: The paneDead property check at line 1031 is optional
and treating undefined as live can cause false positives in the
contradictory-empty rejection path. Replace the condition checking
entry?.target?.paneDead === true with a proper call to isWindowAlive function to
accurately determine if the window is alive. Return false when the window is not
alive, ensuring that undefined paneDead values do not incorrectly bypass the
guard logic that applies to the same check at lines 1090-1092.

---

Outside diff comments:
In `@src/multiplexer/dashboard-control.ts`:
- Around line 918-927: The markProjectServiceEndpointCurrent function is being
called unconditionally after any successful HTTP response, but GET requests
bypass the pid/projectStateDir/manifest verification checks. This allows stale
endpoints to be marked as "current" based on unverified GET responses, causing
subsequent POST requests to incorrectly trust the cached endpoint state instead
of re-verifying with endpointMatchesCurrentProjectService. Only invoke
markProjectServiceEndpointCurrent when the request has been fully verified (for
POST requests), not after all successful responses. Additionally, review the
related caching logic in the lines mentioned (975-986) to ensure similar
protection is applied where dashboardProjectServiceEndpointHealth is accessed.

---

Nitpick comments:
In `@src/metadata-server.test.ts`:
- Around line 205-217: Replace the fixed setTimeout calls (250ms and 20ms) with
vi.waitFor() conditions that wait for observable state changes instead of
relying on timing. After server.notifyChange() is called, use vi.waitFor() to
wait for getState to be called exactly 2 times before making the third fetch
request. Similarly, replace the initial 250ms sleep with vi.waitFor() waiting
for getState to be called exactly 1 time before fetching the cached state. This
ensures the test waits for actual state changes rather than arbitrary delays,
making it deterministic in CI environments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82835eed-0691-4653-8791-c09546e16ed8

📥 Commits

Reviewing files that changed from the base of the PR and between e001f38 and 908e6b5.

📒 Files selected for processing (17)
  • src/builtin-metadata-watchers.test.ts
  • src/builtin-metadata-watchers.ts
  • src/daemon.test.ts
  • src/daemon.ts
  • src/main.ts
  • src/metadata-server.test.ts
  • src/metadata-server.ts
  • src/multiplexer/dashboard-control.test.ts
  • src/multiplexer/dashboard-control.ts
  • src/multiplexer/dashboard-model-service.test.ts
  • src/multiplexer/dashboard-model.ts
  • src/multiplexer/session-launch.test.ts
  • src/multiplexer/session-launch.ts
  • src/project-scanner.test.ts
  • src/tmux/doctor.test.ts
  • src/tmux/runtime-manager.test.ts
  • src/tmux/runtime-manager.ts

Comment thread src/daemon.ts Outdated
Comment thread src/metadata-server.ts
Comment thread src/multiplexer/dashboard-model.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/metadata-server.ts (1)

687-687: 📐 Maintainability & Code Quality | 🟠 Major

Include generated dist/ artifacts in the PR.

The changes to src/metadata-server.ts modify runtime behavior (cache TTL and desktop state refresh logic). Per the coding guideline for src/**/*.ts, these changes require yarn build to update the dist/ directory. The PR currently includes only the source file changes without the corresponding generated artifacts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/metadata-server.ts` at line 687, The modifications to the
DESKTOP_STATE_CACHE_TTL_MS constant and related runtime behavior in
src/metadata-server.ts require regenerating the dist directory artifacts. Run
yarn build to compile the TypeScript source files and generate the updated dist
artifacts, then include these generated files in the PR commit to ensure the
changes are properly reflected in the distributable code.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/tmux/runtime-manager.ts`:
- Around line 794-807: Rebuild the session scan list after calling
repairLegacyProjectSessionNames() in runtime-manager.ts, because the current
sessionNames mapping/filtering still uses the stale allSessionNames snapshot.
Update this logic around getLegacyProjectSessionName(), listSessionNames(), and
repairLegacyProjectSessionNames() so the post-repair names are re-read before
filtering, ensuring renamed legacy client sessions are included immediately even
when the canonical host session already exists.

---

Outside diff comments:
In `@src/metadata-server.ts`:
- Line 687: The modifications to the DESKTOP_STATE_CACHE_TTL_MS constant and
related runtime behavior in src/metadata-server.ts require regenerating the dist
directory artifacts. Run yarn build to compile the TypeScript source files and
generate the updated dist artifacts, then include these generated files in the
PR commit to ensure the changes are properly reflected in the distributable
code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8de8dfb2-50ef-43e7-a969-39ada5ad0813

📥 Commits

Reviewing files that changed from the base of the PR and between 908e6b5 and 6be5105.

📒 Files selected for processing (8)
  • src/metadata-server.test.ts
  • src/metadata-server.ts
  • src/multiplexer/dashboard-control.test.ts
  • src/multiplexer/dashboard-control.ts
  • src/multiplexer/session-launch.test.ts
  • src/multiplexer/session-launch.ts
  • src/tmux/runtime-manager.test.ts
  • src/tmux/runtime-manager.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/multiplexer/session-launch.test.ts
  • src/multiplexer/session-launch.ts
  • src/metadata-server.test.ts
  • src/multiplexer/dashboard-control.ts

Comment thread src/tmux/runtime-manager.ts Outdated
@TraderSamwise

Copy link
Copy Markdown
Owner Author

Follow-up review fixes landed in 6be5105 and a02a4ac:

  • Fixed the POST identity-guard fallthrough: verified endpoints no longer fall through into the unverified/reconnect path.
  • Validate dashboard stream endpoints with /health before use; stale/wrong-project endpoints are removed and restarted.
  • Block mutating requests when endpoint identity is unknown instead of sending to an unverified service.
  • Refresh dirty desktop-state cache synchronously and catch async refresh failures.
  • Repair legacy tmux host/client session names before project-service snapshots and managed-window listing.
  • Added daemon health-probe timeouts and tightened the contradictory-empty guard with isWindowAlive.

Validation: yarn typecheck, yarn lint, yarn vitest, yarn build, and push hook typecheck && lint && vitest run all passed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant