Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions docs/specs/remote-acp-control/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Remote ACP Control Plan

## Summary

Implement ACP-aware remote control inside the existing Electron main remote-control flow. The change stays within the current presenter and runner architecture: `RemoteControlPresenter` handles config normalization and sanitization, `RemoteConversationRunner` handles ACP session creation and workdir resolution, command routers expose ACP-specific behavior, and `RemoteSettings.vue` exposes the new configuration surface.

## Goals

- Allow Telegram and Feishu to use any enabled agent as the default remote agent.
- Require and resolve a workdir when that default agent is ACP.
- Preserve current DeepChat remote behavior.
- Keep the change additive and compatible with existing bindings and settings.

## Readiness

- No open clarification markers remain.
- Scope is intentionally limited to channel-level defaults and ACP-aware session creation.

## Implementation Decisions

### 1. Default Agent Sanitization

- Extend remote default-agent sanitization from “enabled DeepChat only” to “any enabled agent”.
- Fallback order stays deterministic:
- configured candidate
- `deepchat`
- first enabled agent
- literal `deepchat`

### 2. Workdir Storage And Resolution

- Add `defaultWorkdir` to both Telegram and Feishu remote settings/runtime config.
- Normalize missing or blank values to `''`.
- Resolve ACP workdir with this chain:
- channel `defaultWorkdir`
- global `configPresenter.getDefaultProjectPath()`
- explicit runtime error

### 3. ACP Session Creation Path

- `RemoteConversationRunner.createNewSession(...)` checks the resolved default agent type.
- DeepChat path remains unchanged.
- ACP path calls `createDetachedSession(...)` with:
- `agentId`
- `providerId: 'acp'`
- `modelId: agentId`
- `projectDir: resolvedWorkdir`
- If no workdir is available, the runner throws:
- `ACP agent requires a workdir. Set a Remote default directory or global default directory first.`

### 4. Router Behavior

- `/new` continues to create a session through the runner and therefore inherits ACP-aware behavior.
- `/sessions` still scopes to the current bound session agent when present, else the channel default agent.
- `/model` checks whether the current session model is locked by ACP. If locked, it returns:
- `ACP sessions lock the model. Change the channel default agent instead.`
- `/status` includes:
- `Default workdir`
- `Current workdir`

### 5. Settings UI

- The default-agent selector shows all enabled agents.
- ACP agents display with a ` (ACP)` suffix.
- Telegram and Feishu settings each expose a `Default directory` input.
- Helper text explains that the field is used only for ACP and falls back to the global default project path.

## Dependencies

- `RemoteControlPresenter`
- `RemoteConversationRunner`
- `RemoteCommandRouter`
- `FeishuCommandRouter`
- `RemoteSettings.vue`
- Shared remote-control presenter types

## Migration And Compatibility

- No database migration is required.
- Electron Store config is additive and normalized on read/write.
- Existing bindings are not rebound when the default agent or workdir changes.
- Existing remote tool-interaction handling remains attached to the bound session flow.

## Risks And Mitigations

- Missing ACP workdir causes remote session creation failure
- Mitigation: explicit error message and `/status` visibility for default/current workdir
- Invalid configured default agent
- Mitigation: sanitize through enabled-agent fallback order
- UI confusion between DeepChat and ACP agents
- Mitigation: ACP label suffix in selector and helper text for default directory

## Test Strategy

- Main-process tests
- remote default-agent sanitization accepts enabled ACP agents
- ACP session creation passes provider/model/projectDir
- global default project path is used as fallback
- missing workdir throws the documented error
- ACP `/model` path returns the locked-model message
- `/status` includes default/current workdir
- Renderer tests
- enabled ACP agents appear in the selector
- default workdir persists from the settings form
- Regression tests
- DeepChat remote flows continue to pass unchanged
50 changes: 50 additions & 0 deletions docs/specs/remote-acp-control/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Remote ACP Control

## Summary

Extend remote control so Telegram and Feishu can create and continue ACP-backed sessions for coding-style tasks. Each channel keeps a single configured default agent. When that default agent is ACP, remote session creation must resolve a workdir before creating the detached session.

This increment builds on [telegram-remote-control](../telegram-remote-control/spec.md) and [remote-tool-interactions](../remote-tool-interactions/spec.md) without replacing their scope.

## User Stories

- As a remote-control user, I can set an ACP agent as the default remote agent for Telegram or Feishu.
- As a remote-control user, I can configure a per-channel default workdir for ACP sessions.
- As a paired remote user, my first `/new` or plain-text conversation turn can create an ACP session that already knows which workdir to use.
- As a paired remote user, I can inspect the current and default workdir from `/status`.
- As a paired remote user on an ACP-backed session, I get a clear response that `/model` cannot change the session model remotely.

## Acceptance Criteria

- The remote settings UI lists all enabled agents in the default-agent selector, including ACP agents.
- ACP agents are visually labeled in the selector so they are distinguishable from DeepChat agents.
- Telegram and Feishu remote settings both persist a `defaultWorkdir` string in Electron Store.
- New remote sessions still use the configured default agent for the channel.
- When the default agent is DeepChat, remote session creation behavior remains unchanged.
- When the default agent is ACP, remote session creation passes `providerId: 'acp'`, `modelId: <agentId>`, and `projectDir: <resolvedWorkdir>` to detached session creation.
- ACP workdir resolution follows `channel.defaultWorkdir -> global default project path -> explicit error`.
- If neither the channel default workdir nor the global default project path is configured, ACP remote session creation is rejected with an actionable error message.
- `/status` for Telegram and Feishu includes both the default workdir and the current session workdir when available.
- `/model` on an ACP-backed bound session returns a locked-model message instead of opening provider/model selection.
- Existing bound sessions remain bound after changing the channel default agent or default workdir.
- Existing DeepChat remote flows, including `/sessions`, `/use`, `/open`, and remote tool interactions, continue to work.

## Constraints

- Each remote channel keeps one configured default agent; this feature does not add per-message agent switching.
- ACP remote session creation requires a workdir.
- Remote bot text remains English.
- Config changes stay in Electron Store; no SQLite migration is introduced.

## Non-Goals

- No `/agent` remote command.
- No `/workdir` remote command.
- No multi-workspace picker or per-session workspace selection flow.
- No remote provider/model switching for ACP sessions.

## Compatibility

- Existing remote bindings remain valid.
- Existing remote settings load even when `defaultWorkdir` is absent and normalize to an empty string.
- DeepChat remains a valid fallback default agent when an invalid configured agent is encountered.
67 changes: 67 additions & 0 deletions docs/specs/remote-acp-control/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Remote ACP Control Tasks

## Readiness

- No open clarification items remain.
- All tasks below map back to the acceptance criteria in [spec.md](./spec.md).

## T0 Spec Artifacts

- [x] Create `spec.md`, `plan.md`, and `tasks.md`
- Owner: Remote control maintainer
- Acceptance Criteria:
- The ACP remote-control scope, compatibility, and non-goals are documented.
- Reviewers can trace implementation decisions without reading every changed file.

## T1 Remote Config And Type Support

- [x] Add `defaultWorkdir` to Telegram and Feishu remote settings/runtime types
- [x] Normalize and persist `defaultWorkdir` through `RemoteControlPresenter`
- [x] Allow enabled ACP agents to survive remote default-agent sanitization
- Owner: Electron main
- Acceptance Criteria:
- Settings load without migrations.
- ACP is selectable as a valid default remote agent.

## T2 ACP Session Creation

- [x] Detect ACP default agents during remote session creation
- [x] Resolve ACP workdir from channel config or global default project path
- [x] Create detached ACP sessions with `providerId`, `modelId`, and `projectDir`
- [x] Reject ACP session creation when no workdir is configured
- Owner: Electron main
- Acceptance Criteria:
- New ACP remote sessions are created with the expected runtime fields.
- Missing workdir produces the documented error.

## T3 Remote Command Behavior

- [x] Keep `/sessions` agent scoping based on the bound session or default agent
- [x] Add ACP model-lock handling to `/model`
- [x] Add default/current workdir lines to `/status`
- Owner: Remote routers
- Acceptance Criteria:
- ACP sessions do not open model-selection flows.
- `/status` exposes enough context to debug remote workspace selection.

## T4 Settings UI And i18n

- [x] Show all enabled agents in the default-agent selector
- [x] Label ACP agents with `(ACP)`
- [x] Add Telegram and Feishu default-directory inputs
- [x] Add i18n strings for the new settings fields
- Owner: Renderer settings
- Acceptance Criteria:
- Users can configure ACP defaults from the existing Remote settings page.
- The UI still behaves correctly for legacy settings payloads.

## T5 Tests And Validation

- [x] Add main-process tests for ACP sanitization, workdir fallback, and ACP session creation
- [x] Add router tests for ACP `/model` locking and `/status` output
- [x] Add renderer tests for ACP agent visibility and default-workdir persistence
- [x] Run targeted main and renderer Vitest suites
- Owner: QA + Remote control maintainer
- Acceptance Criteria:
- Test coverage maps to the new ACP remote-control behavior.
- DeepChat regression paths remain green in the targeted suites.
65 changes: 65 additions & 0 deletions docs/specs/remote-block-streaming/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Remote Block Streaming Plan

## Summary

Reuse the shared remote block renderer to derive two remote outputs:

- `statusText` for the temporary status message
- `text` / `finalText` for the streamed answer message

Telegram and Feishu both switch from “status message becomes final answer” to “temporary status + persistent streamed answer”.

## Key Decisions

- Keep `RemoteConversationSnapshot` fields:
- `statusText`
- `text`
- `finalText`
- compatibility fields: `draftText`, `renderBlocks`, `fullText`
- Redefine remote runtime use of `text`:
- answer-content only
- may update throughout execution
- Continue to build `RemoteRenderableBlock` data for compatibility and local display, but stop using those blocks as the primary remote transport payload.
- Keep deterministic tool-result summarization and final fallback generation.

## Data Flow

- `DeepChat` stream accumulation still finalizes narrative/tool/search/error blocks as before.
- `RemoteConversationRunner` parses assistant blocks and builds:
- `statusText`
- `text` as answer-only streamed content
- `finalText`
- compatibility fields (`draftText`, `renderBlocks`, `fullText`)
- Telegram runtime:
- creates one temporary status message
- creates one streamed answer message when answer content first appears
- edits the status message as `statusText` changes
- edits the streamed answer message as `text` grows
- deletes the status message after syncing the streamed answer to `finalText`
- Feishu runtime:
- mirrors the same two-message lifecycle with text updates and message deletion
- Pending interactions:
- set the status message to waiting
- keep any already-streamed answer content visible
- continue using the existing prompt/card delivery

## Risks And Mitigations

- Long answers can exceed platform message limits mid-stream
- Mitigation: split answer text into chunks, keep earlier chunks fixed, and continue editing only the newest tail chunk
- A resumed pending interaction could accidentally create duplicate temporary status messages
- Mitigation: keep endpoint-scoped in-memory delivery state with `sourceMessageId`, `statusMessageId`, and `contentMessageIds`
- Status-message deletion may fail due to platform constraints
- Mitigation: treat deletion as best-effort and always clear local transient state

## Test Strategy

- Block renderer unit tests for answer-only `text`, status extraction, and final fallback behavior
- Runner tests for `text` / `statusText` / `finalText` generation across reasoning, writing, and waiting states
- Telegram runtime tests for:
- separate status and answer messages
- temporary status deletion
- streamed answer updates
- long-answer editable tail behavior
- pending prompt handling
- Feishu runtime tests for the same lifecycle plus card fallback behavior
41 changes: 41 additions & 0 deletions docs/specs/remote-block-streaming/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Remote Block Streaming

## Summary

Keep the shared remote block renderer, but move Telegram and Feishu to a dual-track remote delivery model:

- one temporary status message that only shows execution state
- one answer-text message that streams user-visible answer content

The status message is deleted when the turn finishes. The answer-text message remains as the remote transcript for that turn.

## User Stories

- As a Telegram remote user, I can see answer text appear progressively without also receiving reasoning/tool/search transcript spam.
- As a Feishu remote user, I get the same streaming answer experience with a separate execution-status indicator.
- As a remote user, I can still receive pending permission/question prompts as separate actionable messages while preserving any answer text that has already streamed.
- As a remote user, I keep the final answer in chat history while the temporary status message disappears after the turn ends.

## Acceptance Criteria

- Remote snapshot generation continues to expose `statusText`, `text`, and `finalText`, while keeping `draftText`, `renderBlocks`, and `fullText` for compatibility.
- During execution, `text` contains only streamable answer content from `content` blocks; `reasoning_content`, tool, search, and image transcript text never enters the answer stream.
- `statusText` still reflects the current phase, such as thinking, calling a tool, reviewing search results, writing, or waiting for user input.
- Telegram creates a temporary status message and a separate streamed answer message for a normal assistant turn.
- Feishu creates a temporary status message and a separate streamed answer message for a normal assistant turn.
- The status message is updated in place during execution and deleted when the turn completes, errors, times out, or produces no response.
- The answer-text message is updated in place while it fits within the platform limit. When it grows beyond the limit, earlier chunks remain fixed and only the newest tail chunk stays editable.
- Pending interactions set the status message to a waiting state, preserve already-streamed answer text, and continue to use the existing Telegram prompt / Feishu card flow.
- `/status` no longer exposes Telegram stream mode information.

## Constraints

- No extra model call is allowed to summarize tool output or synthesize status text.
- Desktop-local message rendering and persistence remain unchanged.
- No binary image upload support is added for remote channels.

## Compatibility

- Existing remote command routing and pending interaction behavior remain unchanged.
- Old code paths that still read `draftText`, `renderBlocks`, or `fullText` continue to have fallback values.
- Legacy `TelegramStreamMode` config remains readable for compatibility, but remote delivery no longer changes behavior based on it.
37 changes: 37 additions & 0 deletions docs/specs/remote-block-streaming/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Remote Block Streaming Tasks

## T0 Spec

- [x] Update the spec, plan, and tasks artifacts for dual-track remote delivery

## T1 Snapshot Contract

- [x] Keep `statusText`, `text`, and `finalText` in `RemoteConversationSnapshot`
- [x] Redefine `text` as answer-only streamed content for remote runtimes
- [x] Keep `draftText`, `renderBlocks`, and `fullText` as compatibility fields

## T2 Compact Extraction

- [x] Derive short status strings from assistant blocks
- [x] Derive streamed answer text from `content` blocks only
- [x] Keep `finalText` limited to final answer or terminal fallback

## T3 Telegram Delivery

- [x] Create separate temporary status and streamed answer messages
- [x] Delete the status message after final answer sync
- [x] Keep pending interaction prompts separate and preserve streamed answer text
- [x] Support long-answer chunk growth with an editable tail

## T4 Feishu Delivery

- [x] Create separate temporary status and streamed answer messages
- [x] Delete the status message after final answer sync
- [x] Keep pending interaction cards/fallback text separate and preserve streamed answer text
- [x] Support long-answer chunk growth with an editable tail

## T5 Validation

- [x] Add formatter, runner, binding-store, Telegram, and Feishu regression tests
- [x] Keep `/status` free of stream-mode output and retain legacy stream-mode config as a compatibility no-op
- [x] Run repo quality gates and capture residual issues
Loading
Loading