Skip to content

feat: Smart Remote Control (Approvals & Action Timeline)#10

Open
alexchaomander wants to merge 7 commits intomainfrom
feat/smart-remote-control
Open

feat: Smart Remote Control (Approvals & Action Timeline)#10
alexchaomander wants to merge 7 commits intomainfrom
feat/smart-remote-control

Conversation

@alexchaomander
Copy link
Copy Markdown
Owner

Summary

This PR implements the "Smart Remote Control" layer for CloudCode, transforming it from a passive terminal mirror into an active agent management platform. Inspired by specialized tools like claude-watch, these features are built to be agent-agnostic, working with any CLI tool that uses standard terminal conventions.

Key Features

  • Heuristics Engine (Backend): A new semantic parsing engine that runs a headless xterm instance per session to track the "visual state" of the terminal. It detects interactive prompts and tool-use boundaries with high precision.
  • Smart Approval Modals (Mobile-First): Automatically detects [y/n] and "Press Enter" prompts. Instead of opening the mobile keyboard, a native UI card slides up with massive, tap-friendly buttons to Approve or Deny.
  • Action Timeline: Intercepts agent tool-use events (like Claude's tool boxes or generic shell commands) and renders them as premium, collapsible cards in a new Timeline tab. This provides a high-signal "Task Feed" that is easy to skim on the go.
  • Resource Management: Full cleanup logic to ensure headless terminal instances are disposed of when sessions end.

Technical Details

  • Backend: backend/src/terminal/heuristics.ts contains the core logic.
  • Frontend Hook: useTerminal.ts now synchronizes prompt and action states in real-time.
  • UI Components: ActionTimeline.tsx for the card feed and updated Terminal.tsx for the native overlay.

Test Plan

  • Backend tests pass (npm run test)
  • Frontend builds cleanly (npm run build)
  • Verified resource cleanup on WebSocket close.
  • Verified regex patterns against Claude and standard Bash prompts.

🤖 Generated with Claude Code

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a 'Smart Remote Control' feature, which includes a heuristic engine to detect agent activity and transform it into a task timeline, along with interactive approval modals for terminal prompts. The implementation adds a HeuristicsEngine on the backend to parse PTY data and corresponding frontend components to display the timeline and handle prompts. Feedback focuses on improving the robustness of the heuristic detection, specifically addressing issues where tool labels changing mid-execution could cause orphaned timeline entries, ensuring multi-byte characters are handled correctly in the terminal buffer, and making shell command detection more flexible for varied prompt environments.

Comment on lines +150 to +159
if (!this.activeAction || this.activeAction.label !== label || this.activeAction.status !== 'running') {
this.activeAction = {
id: nanoid(8),
type,
label,
status: 'running',
startTime: new Date().toISOString()
};
return this.activeAction;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This logic creates a new action with a new ID whenever the label changes (e.g., when the 'Working...' placeholder is replaced by the actual command in a subsequent chunk). This results in orphaned 'running' entries in the UI timeline that never complete. Instead, the existing activeAction should be updated if it is already running.

      if (this.activeAction && this.activeAction.status === 'running') {
        if (this.activeAction.label !== label) {
          this.activeAction.label = label;
          this.activeAction.type = type;
          return this.activeAction;
        }
        return null;
      }

      this.activeAction = {
        id: nanoid(8),
        type,
        label,
        status: 'running',
        startTime: new Date().toISOString()
      };
      return this.activeAction;

Comment on lines +52 to +53
const chunk = Buffer.from(chunkBase64, 'base64').toString('utf8');
this.term.write(chunk);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Converting raw PTY data to a UTF-8 string before writing to xterm can lead to corrupted output if a multi-byte character is split across chunks. Additionally, this.term should be checked for nullity to avoid potential crashes if data arrives after the engine has been disposed (e.g., during WebSocket closure).

Suggested change
const chunk = Buffer.from(chunkBase64, 'base64').toString('utf8');
this.term.write(chunk);
if (!this.term) return {};
const chunk = Buffer.from(chunkBase64, 'base64');
this.term.write(chunk);

Comment on lines +164 to +172
const lastLine = lines[lines.length - 1] || '';
// Looks for └──────────┘ or ╰──────────╯
if (lastLine.match(/[└╰]─+┘/)) {
this.activeAction.status = 'completed';
this.activeAction.endTime = new Date().toISOString();
const completedAction = { ...this.activeAction };
this.activeAction = null;
return completedAction;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Checking only the very last line for the completion marker is fragile. If the PTY sends the border followed by a prompt or newline in the same chunk, the marker will be on a previous line and the action will stay in the 'running' state. Scanning the last few lines is more robust.

Suggested change
const lastLine = lines[lines.length - 1] || '';
// Looks for └──────────┘ or ╰──────────╯
if (lastLine.match(/[]+/)) {
this.activeAction.status = 'completed';
this.activeAction.endTime = new Date().toISOString();
const completedAction = { ...this.activeAction };
this.activeAction = null;
return completedAction;
}
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 3); i--) {
if (lines[i].match(/[]+/)) {
this.activeAction.status = 'completed';
this.activeAction.endTime = new Date().toISOString();
const completedAction = { ...this.activeAction };
this.activeAction = null;
return completedAction;
}
}

if (!this.activeAction) {
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 3); i--) {
const line = lines[i];
const bashMatch = line.match(/^[\$]\s+([a-zA-Z0-9].+)$/);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The regex ^[$]\s+ is too restrictive as it only matches lines starting exactly with a dollar sign. Most shell prompts include user, host, or path information before the prompt character. Consider allowing leading characters to make this fallback more effective across different environments.

Suggested change
const bashMatch = line.match(/^[\$]\s+([a-zA-Z0-9].+)$/);
const bashMatch = line.match(/[$#]\s+([a-zA-Z0-9].+)$/);

- Fix UTF-8 byte splitting: add decodeUtf8WithCarryover() to handle
  incomplete multi-byte sequences at chunk boundaries
- Fix fragile completion detection: scan all lines instead of just the
  last line to detect tool completion markers
- Fix orphaned running actions: update existing action label instead
  of creating duplicate when label changes during execution
- Add null check for disposed terminal in process()
- Expand shell prompt regex to match $, #, >, ❯, λ prompts
- Add stale action timeout: mark actions as 'error' after 5 minutes
  to prevent stuck 'running' states

🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Cap timelineActions array at 100 items to prevent memory growth
- Remove unused status parameter from ActionIcon component
- Add 20 unit tests for HeuristicsEngine covering UTF-8 handling,
  resource management, edge cases, and large output scenarios
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