Skip to content

feat: ACP filesystem/terminal capabilities + /acp session control menu #40

@fengtality

Description

@fengtality

Overview

Two related features that together enable Condor users to edit Hummingbot config files, create strategies, and manage running bots directly from Telegram via Claude Code — without leaving the chat.

  • Feature A: Implement ACP filesystem + terminal capabilities so Claude Code can read/write files and run shell commands on behalf of the user
  • Feature B: Add a /acp inline menu giving users operational control over ACP sessions (steer, permissions, cwd, model, sessions list)

These are intentionally left unimplemented for now; this issue tracks the design and intent.


Background

Condor already acts as an ACP client, spawning Claude Code via claude-agent-acp and exchanging session/new + session/prompt messages over stdio. The core flow works. What's missing is:

  1. ACP optional capabilities — Condor doesn't advertise fs or terminal capabilities during the initialize handshake, so Claude Code never attempts file or shell operations.
  2. Session control UI — The current /agent menu has basic start/stop/status but no way to steer a running session, change the working directory, set permission levels, switch models, or see all active sessions.

Feature A: ACP Filesystem + Terminal Capabilities

What Needs to Be Implemented

Capability advertisement (in condor/acp/client.py, initialize response):

{
    "capabilities": {
        "fs": {
            "readTextFile": True,
            "writeTextFile": True
        },
        "terminal": True
    }
}

Filesystem handlers (agent → Condor callbacks):

Method Params Returns
fs/read_text_file {path: str} {content: str}
fs/write_text_file {path: str, content: str} {}

Terminal handlers:

Method Params Returns
terminal/create {command: [str], cwd?: str, env?: dict} {terminalId: str}
terminal/output {terminalId: str} {output: str, exitCode?: int}
terminal/release {terminalId: str} {}
terminal/wait_for_exit {terminalId: str, timeout?: int} {exitCode: int}
terminal/kill {terminalId: str} {}

Security Model

This is the most critical part. Since Condor is a Telegram bot, arbitrary-path access must be prevented.

Read allowlist (paths Claude Code may read without approval):

~/hummingbot-api/bots/
~/hummingbot/conf/
~/hummingbot-skills/
~/.agents/

Write allowlist (writes only permitted inside):

~/hummingbot-api/bots/instances/*/conf/controllers/
~/hummingbot-api/bots/instances/*/conf/scripts/
~/hummingbot-api/bots/controllers/
~/hummingbot-api/bots/scripts/

Write approval gate: Every fs/write_text_file call must pause execution and send a Telegram message showing a unified diff with [✅ Approve] / [❌ Cancel] buttons before the write occurs. Same pattern as session/request_permission.

Terminal approval gate: Every terminal/create call shows the full command + working directory to the user before spawning. A hardcoded blocklist rejects obviously dangerous patterns (rm -rf /, curl | bash, writes outside allowed dirs, etc.).

Audit log: When operating in approve-all mode (see Feature B), auto-approved writes and terminal commands must still post a silent notification to Telegram so nothing is invisible.

Terminal Implementation Notes

  • Use asyncio.create_subprocess_exec (not shell=True) with stdout+stderr piped
  • In-memory terminal registry: dict[terminalId, asyncio.subprocess.Process] + output buffer per terminal
  • Background task per terminal reads stdout continuously and appends to buffer
  • terminal/output drains the buffer and returns current exit code (None if still running)
  • terminal/wait_for_exit polls until returncode is not None or timeout

Proposed File Structure

condor/acp/
├── client.py          ← add _handle_fs_* and _handle_terminal_* dispatchers
├── fs.py              ← NEW: path validation, read/write, diff generation, approval flow
└── terminal.py        ← NEW: subprocess registry, output buffering, approval gate

End-to-End UX Example

User: "Update my perc_sell_rebalancer to use position_width_pct=30 and restart the bot"

(Claude Code calls fs/read_text_file on the controller YAML — auto-approved)

Condor → Telegram:
📝 Claude wants to edit a file:
perc_sell_rebalancer.yml

- position_width_pct: 40
+ position_width_pct: 30

[✅ Approve] [❌ Cancel]

(User approves → file written)

Condor → Telegram:
💻 Claude wants to run:
docker restart perc_sell_bot-20260227-...
[✅ Run] [❌ Cancel]

(User approves → output streamed back)

Condor: ✅ Bot restarted.


Feature B: /acp Inline Menu

Motivation

OpenClaw (reference implementation) exposes a full /acp command family: spawn, cancel, steer, close, status, cwd, permissions, model, sessions, doctor. Condor's current /agent menu covers spawn/cancel/close/status but is missing the operational controls that become essential once filesystem and terminal capabilities are live.

Commands to Implement (Priority Order)

1. Steer — highest leverage, no security implications
Inject a mid-session instruction without losing context. User taps [⚡ Steer], sends a message, it's delivered to the running Claude Code session as a steering nudge. Avoids needing to start a fresh session when you just want to redirect focus.

2. Permissions — required before fs/terminal goes live
Surface the permission mode as selectable buttons:

Mode Behavior
approve-reads Reads auto-approved; writes + exec require per-operation approval (default)
approve-all All operations auto-approved; audit log sent to Telegram
deny-all All write/exec attempts denied gracefully

Stored per-user in the existing pickle store.

3. CWD picker — required for Hummingbot config editing
Preset list (not free text) to avoid path injection:

  • ~/hummingbot-api/bots/ — bot instances and configs
  • ~/hummingbot/conf/ — connectors and global settings
  • ~/hummingbot-skills/ — skills repo
  • ~/condor/ — Condor source
  • Custom path (requires explicit confirm step)

4. Sessions list — operational visibility
List active + recent sessions with key, label, model, start time, and context usage %. One tap to resume or close any session.

5. Model switcher — cost control
Switch mid-session between configured models (e.g. Claude Sonnet → Haiku → Gemini Flash).

Commands to Skip / Defer

  • Thread binding — Telegram topics work but most Condor users are in DMs; low value for now
  • /acp set-mode — Depends on session/set_mode being exposed by the agent; not confirmed available
  • /acp timeout — Fine to hardcode 120s for now
  • /acp doctor — Nice to have eventually; low priority
  • /acp install — Operator tooling, not user-facing

Proposed Menu Structure

/acp  →  inline menu

🤖 ACP Session
───────────────
[▶ Start]   [⏹ Stop]   [📊 Status]

── Controls ──
[⚡ Steer]     [📁 CWD]
[🔐 Permissions]  [🧠 Model]

── Sessions ──
[📋 List]

Proposed File Structure

handlers/agents/
├── __init__.py        ← add /acp alias, extend dispatch
├── menu.py            ← extend with new buttons
├── acp/               ← NEW subdirectory
│   ├── steer.py       ← inject steer into active session
│   ├── permissions.py ← permissionMode state management per user
│   ├── cwd.py         ← preset + custom cwd selector
│   ├── sessions.py    ← list/resume/close sessions
│   └── model.py       ← model switcher

Implementation Order

  1. Feature B: Steer (no security implications, immediately useful)
  2. Feature B: Permissions UI (required before fs/terminal)
  3. Feature A: fs/read_text_file (low risk, no approval gate needed)
  4. Feature A: fs/write_text_file + write approval gate
  5. Feature B: CWD picker
  6. Feature A: terminal/create + terminal approval gate + remaining terminal methods
  7. Feature B: Sessions list
  8. Feature B: Model switcher

Reference

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions