Skip to content

feat: preserve OpenClaw main-session continuity for TUI gateway client#48589

Closed
doskoi wants to merge 1 commit intoopenclaw:mainfrom
doskoi:feat/openclaw-main-session-continuity-p0-p1
Closed

feat: preserve OpenClaw main-session continuity for TUI gateway client#48589
doskoi wants to merge 1 commit intoopenclaw:mainfrom
doskoi:feat/openclaw-main-session-continuity-p0-p1

Conversation

@doskoi
Copy link
Copy Markdown

@doskoi doskoi commented Mar 17, 2026

No description provided.

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui gateway Gateway runtime size: L labels Mar 17, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 17, 2026

Greptile Summary

This PR introduces stable TUI session continuity for the gateway client. It does so via three coordinated mechanisms: (1) a per-terminal-slot instance ID (tui-client-instance.ts) that persists across restarts, (2) a new affinity key scheme (client-affinity.ts, agent:<id>:tui:<mainKey>:<instanceId>) that routes the TUI's main session to a deterministic key, and (3) an orphan short-reply reattachment pass in initSessionState that silently routes confirmation/continuation replies (yes, continue, both, etc.) back to the correct awaiting TUI session when they arrive under a new session key.

Key changes:

  • src/tui/tui-client-instance.ts — new module; reads/creates a stable instance ID from disk, keyed by terminal slot env vars (TMUX_PANE, WEZTERM_PANE, etc.)
  • src/gateway/client-affinity.ts — new module; normalises and builds TUI affinity session keys and origin targets
  • src/auto-reply/reply/session.ts — adds findOrphanShortReplyCandidate, intent classifiers, and the SessionKey override in the returned context to reflect any reattachment
  • src/gateway/server-methods/chat.ts — synthesises OriginatingTo from the TUI instance ID so the affinity key can be resolved on the reply path
  • src/tui/tui.ts — updates resolveTuiSessionKey to route the empty/main input to the affinity key and cleans up the session label formatter for the new key shape

Minor issues found:

  • A redundant second regex branch in classifyAwaitingReplyIntent (both is already covered by the first pattern)
  • Two split import statements from the same module path in session.ts
  • isTuiMainSessionAlias is exported from client-affinity.ts but has no current consumers

Confidence Score: 4/5

  • This PR is safe to merge; the new session continuity logic is well-guarded with age limits, affinity matching, and reset-trigger exclusions.
  • The core logic is thoroughly tested (new describe blocks cover the happy path and the "wrong TUI route" guard), and the orphan reattachment only fires under a narrow set of conditions (no existing entry, matching affinity, within 12 h, and short-reply vocabulary match). The only notable issues are stylistic — a dead regex branch, split imports, and an unexported-for-now helper — none of which affect runtime correctness.
  • src/auto-reply/reply/session.ts — minor style issues (redundant regex, split imports)

Comments Outside Diff (1)

  1. src/auto-reply/reply/session.ts, line 172-173 (link)

    Duplicate imports from the same module

    archiveSessionTranscripts and readSessionMessages are both imported from the same path. Splitting them across two import statements is inconsistent with TypeScript/ESM style and will trigger lint rules in most projects. They should be combined into a single named import.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/auto-reply/reply/session.ts
    Line: 172-173
    
    Comment:
    **Duplicate imports from the same module**
    
    `archiveSessionTranscripts` and `readSessionMessages` are both imported from the same path. Splitting them across two `import` statements is inconsistent with TypeScript/ESM style and will trigger lint rules in most projects. They should be combined into a single named import.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/session.ts
Line: 274-276

Comment:
**Redundant second regex check**

The second condition `|| /\b(both)\b/i.test(normalized)` is entirely redundant — `both` is already included in the alternation group of the first regex on the line above. This dead branch will never add any new matches and looks like a copy-paste artefact.

```suggestion
  if (/\b(which|choose|pick|option|a\/b|a or b|1 or 2|one or both|both)\b/i.test(normalized)) {
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/auto-reply/reply/session.ts
Line: 172-173

Comment:
**Duplicate imports from the same module**

`archiveSessionTranscripts` and `readSessionMessages` are both imported from the same path. Splitting them across two `import` statements is inconsistent with TypeScript/ESM style and will trigger lint rules in most projects. They should be combined into a single named import.

```suggestion
import { archiveSessionTranscripts, readSessionMessages } from "../../gateway/session-utils.fs.js";
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/gateway/client-affinity.ts
Line: 52-75

Comment:
**Exported function with no consumers**

`isTuiMainSessionAlias` is exported but has no callers in the current codebase (the only reference is the file itself). If this is scaffolding for a follow-up PR, consider adding a `// TODO` comment explaining the intended consumer, or keeping it unexported until it's actually used — exporting dead code inflates the public API surface and may confuse future readers.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: aad59ac

Comment on lines +274 to +276
/\b(which|choose|pick|option|a\/b|a or b|1 or 2|one or both|both)\b/i.test(normalized) ||
/\b(both)\b/i.test(normalized)
) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Redundant second regex check

The second condition || /\b(both)\b/i.test(normalized) is entirely redundant — both is already included in the alternation group of the first regex on the line above. This dead branch will never add any new matches and looks like a copy-paste artefact.

Suggested change
/\b(which|choose|pick|option|a\/b|a or b|1 or 2|one or both|both)\b/i.test(normalized) ||
/\b(both)\b/i.test(normalized)
) {
if (/\b(which|choose|pick|option|a\/b|a or b|1 or 2|one or both|both)\b/i.test(normalized)) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/session.ts
Line: 274-276

Comment:
**Redundant second regex check**

The second condition `|| /\b(both)\b/i.test(normalized)` is entirely redundant — `both` is already included in the alternation group of the first regex on the line above. This dead branch will never add any new matches and looks like a copy-paste artefact.

```suggestion
  if (/\b(which|choose|pick|option|a\/b|a or b|1 or 2|one or both|both)\b/i.test(normalized)) {
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +52 to +75
export function isTuiMainSessionAlias(params: {
raw?: string | null;
currentAgentId: string;
sessionMainKey: string;
}): boolean {
const trimmed = (params.raw ?? "").trim().toLowerCase();
if (!trimmed) {
return false;
}
if (trimmed === "main") {
return true;
}
const mainKey = normalizeMainKey(params.sessionMainKey);
if (trimmed === mainKey) {
return true;
}
return (
trimmed ===
buildAgentMainSessionKey({
agentId: params.currentAgentId,
mainKey,
}).toLowerCase()
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exported function with no consumers

isTuiMainSessionAlias is exported but has no callers in the current codebase (the only reference is the file itself). If this is scaffolding for a follow-up PR, consider adding a // TODO comment explaining the intended consumer, or keeping it unexported until it's actually used — exporting dead code inflates the public API surface and may confuse future readers.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/client-affinity.ts
Line: 52-75

Comment:
**Exported function with no consumers**

`isTuiMainSessionAlias` is exported but has no callers in the current codebase (the only reference is the file itself). If this is scaffolding for a follow-up PR, consider adding a `// TODO` comment explaining the intended consumer, or keeping it unexported until it's actually used — exporting dead code inflates the public API surface and may confuse future readers.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aad59acdc5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +508 to +512
if (!resetTriggered && !entry) {
const orphanCandidate = findOrphanShortReplyCandidate({
ctx: sessionCtxForState,
sessionKey,
sessionStore,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Limit orphan short-reply reattach to default TUI sessions

The new orphan-short-reply recovery runs for any missing session key, so an explicit session switch (for example /session project-x) can be overridden if the first message is a short token like yes/ok and another recent TUI session on the same OriginatingTo is awaiting confirmation. In that case, sessionKey is silently reassigned to the candidate session, causing the reply to be written to the wrong transcript and defeating the user’s explicit session choice. This should be gated to default/auto main-session aliases only, not arbitrary missing keys.

Useful? React with 👍 / 👎.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 28, 2026

Codex review: needs changes before merge.

What this changes:

The branch adds stable TUI instance IDs, TUI affinity session keys/origin metadata, and orphan short-reply reattachment logic/tests for Gateway TUI main-session continuity.

Required change before merge:

The blockers are narrow, file-level repairs on an otherwise relevant implementation PR, so an automated worker can update the branch or prepare a replacement branch with focused tests and changelog coverage.

Security review:

Security review cleared: The PR diff does not add dependencies, workflows, release scripts, third-party code execution, secret handling, or broader permissions; the new persistent TUI instance state is local OpenClaw state with sanitized path segments.

Review findings:

  • [P1] Gate orphan reattach to default TUI main aliases — src/auto-reply/reply/session.ts:508-509
  • [P2] Match the current TUI client id for affinity origin — src/gateway/server-methods/chat.ts:1253-1254
  • [P3] Add the required changelog entry — src/tui/tui.ts:350
Review details

Best possible solution:

Rebase and repair the branch so stable per-terminal TUI affinity is preserved, origin metadata is synthesized for the real TUI client id, orphan short-reply recovery is limited to default/main TUI aliases, explicit session switching is covered by regression tests, and the user-facing feature gets a changelog entry.

Do we have a high-confidence way to reproduce the issue?

Yes. The source-level reproduction is high-confidence: current main sends TUI traffic as openclaw-tui while the PR fallback checks gateway-client, and the PR's orphan reattach block runs for any missing session key before proving it was a default/main TUI alias.

Is this the best way to solve the issue?

No. The overall direction is reasonable, but this is not yet the best fix because it misses the current client identity and can override explicit session choices; the safer implementation is to match GATEWAY_CLIENT_NAMES.TUI and gate recovery to recognized main-session aliases only.

Full review comments:

  • [P1] Gate orphan reattach to default TUI main aliases — src/auto-reply/reply/session.ts:508-509
    This recovery runs for any missing session key, so an explicit /session project-x whose first message is yes can be silently reassigned to a recent same-affinity TUI session awaiting confirmation. Limit reattachment to default/main TUI aliases and add a regression for explicit session switches.
    Confidence: 0.92
  • [P2] Match the current TUI client id for affinity origin — src/gateway/server-methods/chat.ts:1253-1254
    Current main registers the Gateway TUI as GATEWAY_CLIENT_NAMES.TUI, but this fallback only builds session:tui:<id> for GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT. After rebase, real TUI sends will not carry the affinity origin metadata needed by the reattach path.
    Confidence: 0.88
  • [P3] Add the required changelog entry — src/tui/tui.ts:350
    This is a user-facing TUI feature, but the branch does not touch CHANGELOG.md. Add a single active-version entry with appropriate non-bot credit, for example Thanks @doskoi, before merge.
    Confidence: 0.82

Overall correctness: patch is incorrect
Overall confidence: 0.9

Acceptance criteria:

  • pnpm test src/gateway/server-methods/chat.directive-tags.test.ts src/auto-reply/reply/session.test.ts src/tui/tui.test.ts src/tui/tui-client-instance.test.ts
  • pnpm check:changed in Blacksmith Testbox before handoff

What I checked:

  • Current main TUI default session behavior: The current TUI resolves empty per-sender input through buildAgentMainSessionKey, producing the normal agent main session key rather than a per-client TUI affinity key. (src/tui/tui.ts:150, e26357fee8fc)
  • Current main TUI client identity: GatewayChatClient registers the TUI as GATEWAY_CLIENT_NAMES.TUI with mode UI and a random instanceId, so affinity fallback logic must match openclaw-tui rather than gateway-client. (src/tui/gateway-chat.ts:123, e26357fee8fc)
  • Protocol constant for real TUI client id: The protocol defines GATEWAY_CLIENT_NAMES.TUI as openclaw-tui and GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT as gateway-client, confirming the two ids are distinct. (src/gateway/protocol/client-info.ts:6, e26357fee8fc)
  • PR client-id mismatch: The PR only builds a session:tui origin target when clientInfo.id equals GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT, so rebased real TUI sends using GATEWAY_CLIENT_NAMES.TUI would not populate the affinity metadata. (src/gateway/server-methods/chat.ts:1253, aad59acdc5de)
  • PR orphan reattach scope: The PR runs findOrphanShortReplyCandidate for any missing session key when there is no entry, with no guard that the missing key was a default/main TUI alias. (src/auto-reply/reply/session.ts:508, aad59acdc5de)
  • TUI explicit session contract: The user docs describe explicit TUI session switching as agent-scoped keys, including /session main expanding to agent::main and agent-prefixed sessions being explicit. Public docs: docs/web/tui.md. (docs/web/tui.md:63, e26357fee8fc)

Likely related people:

  • steipete: Recent path history and local blame show repeated maintenance across the exact TUI session resolver, gateway chat client, Gateway chat handler, and session-init areas affected by this PR. (role: recent maintainer; confidence: high; commits: 9d21df251e50, 7e41913a203a, 6bbacd14a366; files: src/tui/tui.ts, src/tui/gateway-chat.ts, src/gateway/server-methods/chat.ts)
  • vincentkoc: Recent commits touch Gateway chat transcript handling, session rollover cleanup, and gateway/client seams adjacent to the origin metadata and session reattachment paths. (role: adjacent owner; confidence: medium; commits: aec83af23d49, bf0d2d70be51, 0f7d9c957093; files: src/gateway/server-methods/chat.ts, src/auto-reply/reply/session.ts, src/tui/gateway-chat.ts)
  • shakkernerd: Recent history on the TUI gateway client startup path includes setup TUI handoff and gateway chat client cleanup/retry work, which is close to the client identity and reconnect continuity surface. (role: adjacent TUI/Gateway maintainer; confidence: medium; commits: aae4b1b29dd7, 4e6dfc015e6b, 3730d6d17a68; files: src/tui/gateway-chat.ts)

Remaining risk / open question:

  • No executable validation was run because this was a read-only sweep.
  • The PR is based on an older base SHA, so exact line numbers may move during rebase even though the defects are tied to stable code paths.

Codex review notes: model gpt-5.5, reasoning high; reviewed against e26357fee8fc.

@BunsDev
Copy link
Copy Markdown
Member

BunsDev commented May 2, 2026

Closing as superseded by #75948. That PR carries the current-main fix for #63195/#73546 and avoids the stale orphan-reattach/client-id review blockers noted on this branch.

@BunsDev BunsDev closed this May 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui gateway Gateway runtime size: L triage: blank-template Candidate: PR template appears mostly untouched.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants