Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ ai-devkit agent send "run the tests and report back" --id <agent-name> --wait
npm test 2>&1 | ai-devkit agent send --id <agent-name> --stdin

# Pipe a session through Telegram — operate your agent from your phone
ai-devkit channel start telegram
ai-devkit channel start telegram --agent <agent-name> --daemon
```

Useful for long-running tasks, scheduled work, or checking on an agent from your phone at lunch.
Expand Down
92 changes: 92 additions & 0 deletions docs/ai/design/feature-channel-daemon-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
phase: design
title: "Channel Daemon Mode: System Design"
description: CLI daemon lifecycle design for ai-devkit channel bridges
---

# System Design: Channel Daemon Mode

## Architecture Overview

`channel start --daemon` is implemented as a CLI process wrapper around an internal bridge runner. The parent CLI validates arguments, checks bridge state, spawns a detached child process, records metadata in the existing bridge registry, and exits. The child runs `channel-daemon.js`, which calls `runChannelBridge()` directly instead of re-entering Commander. Daemon stdout and stderr are appended to a per-channel log file and the parent prints that path after startup.

```mermaid
graph TD
USER[Developer CLI] --> START[channel start --daemon]
START --> STATE[ChannelService bridge registry]
START -->|no live daemon| SPAWN[Detached child process]
SPAWN --> CHILD[channel-daemon.js]
SPAWN --> LOG[(~/.ai-devkit/channel-logs/name.log)]
CHILD --> BRIDGE[runChannelBridge]
BRIDGE --> TELEGRAM[Telegram adapter]
BRIDGE --> AGENT[Agent manager]
STATE --> FILE[(~/.ai-devkit/channel-bridges.json)]
USER --> STOP[channel stop]
STOP --> STATE
STOP -->|SIGTERM| CHILD
CHILD -->|cleanup| STATE
```

## Data Models

### ChannelBridgeProcess
```typescript
interface ChannelBridgeProcess {
channelName: string;
channelType: string;
agentName: string;
agentPid: number;
bridgePid: number;
startedAt: string;
logPath?: string;
}
```

State is stored in the existing `~/.ai-devkit/channel-bridges.json` registry with restrictive permissions when possible. Daemon parent processes initially record `agentPid: 0`; `runChannelBridge()` overwrites the entry with the resolved agent PID after it finishes startup. Daemon logs are stored separately at `~/.ai-devkit/channel-logs/<channel>.log`.

## API Design

### CLI
```text
ai-devkit channel start [name] --agent <name> --daemon
ai-devkit channel stop
```

### Internal Service
```typescript
class ChannelService {
getLiveBridges(): Promise<ChannelBridgeProcess[]>;
startDaemonBridge(input: StartDaemonBridgeInput): Promise<ChannelBridgeProcess>;
stopBridge(channelName?: string): Promise<StopBridgeResult>;
unregisterBridge(channelName: string): Promise<void>;
}
```

The service owns process spawning, PID liveness checks, stale state cleanup, and stop signaling.

## Component Breakdown

- `packages/cli/src/services/channel/channel.service.ts`: bridge registry, daemon spawn, and stop lifecycle.
- `packages/cli/src/services/channel/channel-runner.ts`: reusable foreground bridge runtime.
- `packages/cli/src/channel-daemon.ts`: internal daemon child entrypoint.
- `packages/cli/src/commands/channel.ts`: adds the `--daemon` start option and `stop` subcommand.
- Existing channel bridge code remains the foreground execution path.

## Design Decisions

### Recommended Approach: Detached Child Process
Spawn a detached Node process that runs a dedicated internal daemon entrypoint. The daemon entrypoint calls the same `runChannelBridge()` function used by foreground start. This keeps bridge logic in one place, avoids hidden public CLI flags, and avoids OS-specific service management.

### Alternatives Considered
- **External process manager**: PM2/systemd/launchd would provide mature daemon features, but would add setup burden and platform-specific instructions.
- **Always background by default**: Simpler lifecycle for long-running bridges, but it would break existing users who expect foreground logs and Ctrl-C behavior.
- **Multiple daemon registry**: More flexible, but unnecessary for the current single-user Telegram bridge and increases stop/status complexity.

## Non-Functional Requirements

- Daemon start should return quickly after spawning and recording state.
- Daemon start and status should print the log path so users can inspect background failures.
- Stop should prefer graceful shutdown with `SIGTERM`.
- Stale state should not block future daemon starts.
- Foreground behavior must remain backward compatible.
- Bridge metadata must not include bot tokens or other secrets.
60 changes: 60 additions & 0 deletions docs/ai/implementation/feature-channel-daemon-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
phase: implementation
title: "Channel Daemon Mode: Implementation Guide"
description: Implementation notes for channel daemon start and stop
---

# Implementation Guide: Channel Daemon Mode

## Development Setup

- Work in `feature-channel-daemon-mode`.
- Use `npm ci` from the repository root.
- Run targeted CLI tests during development.

## Code Structure

- `packages/cli/src/commands/channel.ts`: command registration and user-facing messages.
- `packages/cli/src/services/channel/channel-runner.ts`: shared foreground bridge runtime.
- `packages/cli/src/services/channel/channel.service.ts`: bridge registry, daemon spawn, and stop lifecycle.
- `packages/cli/src/channel-daemon.ts`: internal detached child entrypoint.

## Implementation Notes

### Core Features
- `channel start --daemon` validates the same channel and agent arguments as foreground start.
- The parent process spawns a detached `channel-daemon.js` child process.
- The parent process redirects daemon stdout and stderr to `~/.ai-devkit/channel-logs/<channel>.log` and prints that path.
- `channel status` prints the log path for running daemon bridges when it is present in bridge state.
- Foreground start and daemon child execution both call `runChannelBridge()`.
- Dev mode launches `src/channel-daemon.ts` through `ts-node`; built mode launches `dist/channel-daemon.js`.
- `channel stop [name]` reads the existing bridge registry, checks PID liveness, sends `SIGTERM`, and clears state.

### Patterns & Best Practices
- Keep daemon metadata free of secrets.
- Prefer dependency injection for `spawn`, `process.kill`, and filesystem paths in tests.
- Treat stale state as recoverable and remove it before starting a new daemon.

## Integration Points

- Reuse the existing channel resolution and bridge startup logic.
- Persist daemon bridge state in the existing `~/.ai-devkit/channel-bridges.json` registry.
- Persist daemon log location in bridge state as `logPath`.
- Keep channel connector unchanged.

## Error Handling

- No daemon state: print a clear no-op message.
- Stale state: remove it and continue for start, report it for stop.
- Permission or signal failure: show a clear error and keep enough state for manual inspection.

## Performance Considerations

- Daemon start should not block on long-lived bridge loops.
- PID checks should be synchronous and cheap.

## Security Notes

- Do not write bot tokens, chat IDs, or message content to daemon state.
- Treat daemon logs as local operational logs; they capture child process stdout and stderr for debugging.
- Use restrictive file permissions for state files where supported.
62 changes: 62 additions & 0 deletions docs/ai/planning/feature-channel-daemon-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
phase: planning
title: "Channel Daemon Mode: Planning"
description: Task breakdown for adding daemon start and channel stop
---

# Planning: Channel Daemon Mode

## Milestones

- [x] Milestone 1: Document and review daemon requirements/design
- [x] Milestone 2: Implement daemon lifecycle service
- [x] Milestone 3: Wire CLI options and stop command
- [x] Milestone 4: Add focused tests and verification

## Task Breakdown

### Phase 1: Foundation
- [x] Task 1.1: Identify current channel command structure and bridge execution path.
- [x] Task 1.2: Add daemon state model and state file helpers.
- [x] Task 1.3: Add PID liveness checks with stale state cleanup.

### Phase 2: Core Features
- [x] Task 2.1: Add `--daemon` option to `channel start`.
- [x] Task 2.2: Spawn detached child process for daemon start.
- [x] Task 2.3: Add an internal daemon entrypoint to avoid recursive CLI spawning.
- [x] Task 2.4: Add `channel stop` command that terminates the recorded daemon.

### Phase 3: Integration & Polish
- [x] Task 3.1: Prevent duplicate daemon starts while a recorded daemon is alive.
- [x] Task 3.2: Improve user-facing messages for started, stopped, absent, and stale daemon states.
- [x] Task 3.3: Add unit tests for daemon start/stop behavior.
- [x] Task 3.4: Run CLI/package tests and feature lint.

## Dependencies

- Existing `packages/cli/src/commands/channel.ts` bridge implementation.
- Existing channel service tests and config path conventions.
- Node.js `child_process.spawn` and process signaling.

## Timeline & Estimates

- Foundation: Small
- Core CLI lifecycle: Medium
- Tests and verification: Small to medium

## Risks & Mitigation

| Risk | Impact | Mitigation |
|------|--------|------------|
| Detached child cannot be reliably confirmed as healthy immediately | Start may report success before bridge fully connects | Record process spawn success in v1; keep foreground mode for debugging |
| Stale PID points to an unrelated process after PID reuse | Stop could signal the wrong process | Store command metadata and keep stale checks conservative; future work can add heartbeat |
| Duplicate Telegram polling daemons conflict | Bot updates may be consumed unpredictably | Enforce one live daemon state in v1 |

## Resources Needed

- Existing Jest test setup.
- Existing CLI command and channel service patterns.

## Progress Summary

Implemented daemon lifecycle support in the existing channel service and command layer. `channel start --daemon` now spawns a detached `channel-daemon.js` child, records the child PID in the existing bridge registry, and prevents duplicate live bridges. Foreground start and daemon child execution share `runChannelBridge()`. `channel stop [name]` terminates a recorded live bridge with `SIGTERM` and cleans up registry state. Feature lint, CLI lint, CLI build, dependency builds, and the full CLI Jest suite pass.
76 changes: 76 additions & 0 deletions docs/ai/requirements/feature-channel-daemon-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
phase: requirements
title: "Channel Daemon Mode: Requirements"
description: Add background start and stop lifecycle support for ai-devkit channel bridges
---

# Requirements: Channel Daemon Mode

## Problem Statement

`ai-devkit channel start` currently runs the channel bridge as a foreground long-lived process. That works for local debugging, but it ties the bridge lifecycle to an active terminal and makes it awkward to keep a Telegram-to-agent bridge running while doing other work.

Developers need a daemon mode so they can start the bridge in the background, close the invoking shell, and later stop the bridge with a dedicated command.

## Goals & Objectives

### Primary Goals
- Add daemon mode to `ai-devkit channel start`.
- Add `ai-devkit channel stop` to terminate a running daemon bridge.
- Persist enough bridge metadata to make stop/status behavior deterministic.
- Preserve the existing foreground `channel start` behavior by default.

### Secondary Goals
- Keep daemon lifecycle logic inside the CLI package.
- Reuse the existing channel bridge implementation instead of duplicating bridge behavior.
- Make stale daemon state easy to detect and recover from.

### Non-Goals
- Running multiple channel bridge daemons concurrently.
- Implementing OS-specific launch agents, services, or auto-start on boot.
- Adding a new channel connector package API for daemon management.
- Managing or stopping the target AI agent process.

## User Stories & Use Cases

### US-1: Start in the Background
As a developer, I want to run `ai-devkit channel start <channel> --agent <name> --daemon` so that the channel bridge keeps running after the command returns.

### US-2: Stop the Background Bridge
As a developer, I want to run `ai-devkit channel stop` so that the running channel bridge shuts down cleanly.

### US-3: See Useful Feedback
As a developer, I want the start command to print the daemon PID and basic context so that I can tell what is running.

### US-4: Avoid Duplicate Daemons
As a developer, I want daemon start to fail when an existing bridge daemon is already running so that two bridge processes do not compete for the same Telegram bot polling stream.

### US-5: Recover from Stale State
As a developer, I want stale daemon metadata to be ignored or removed when the recorded process is no longer alive so that a crashed daemon does not block future starts.

## Success Criteria

- `ai-devkit channel start <channel> --agent <name>` still runs the bridge in the foreground.
- `ai-devkit channel start <channel> --agent <name> --daemon` spawns a detached bridge process and exits successfully after recording daemon state.
- `ai-devkit channel stop` sends a graceful termination signal to the recorded daemon process and removes daemon state once stopped.
- Stop reports a clear message when no daemon is running.
- Start refuses to launch a second daemon when the recorded PID is alive.
- Unit tests cover daemon start, duplicate prevention, stale state cleanup, and stop behavior.

## Constraints & Assumptions

### Constraints
- Follow existing TypeScript, Commander, Jest, and CLI service patterns.
- Use Node.js standard process primitives for cross-platform compatibility.
- Persist bridge state under the existing `~/.ai-devkit` area.
- Do not require additional runtime dependencies.

### Assumptions
- One daemon bridge at a time is sufficient for the current channel UX.
- The daemon can re-enter the existing `channel start` command internally with a private child-process flag.
- The existing `channel status` command can be extended later if it does not already surface daemon state.

## Questions & Open Items

- Should `channel stop` accept a channel name in the future if multiple concurrent daemons are added?
- Should daemon logs be written to a user-visible file in `~/.ai-devkit`, or should v1 redirect to ignored stdio only?
69 changes: 69 additions & 0 deletions docs/ai/testing/feature-channel-daemon-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
phase: testing
title: "Channel Daemon Mode: Testing Strategy"
description: Test coverage plan for channel daemon start and stop
---

# Testing Strategy: Channel Daemon Mode

## Test Coverage Goals

- Cover all new daemon lifecycle branches in unit tests.
- Keep foreground `channel start` behavior covered by existing tests.
- Avoid tests that require a real Telegram bot or real long-running child process.

## Unit Tests

### ChannelService
- [x] Starts a daemon by spawning a detached child process and writing state.
- [x] Redirects daemon stdout and stderr to the per-channel log file.
- [x] Refuses to start when recorded daemon PID is alive.
- [x] Removes stale state when recorded PID is not alive.
- [x] Stops a running bridge with `SIGTERM` and clears state.
- [x] Requires a channel name when multiple live bridges are running.

### Channel Command
- [x] `channel start --daemon` delegates to daemon service.
- [x] `channel start` without `--daemon` uses existing foreground behavior.
- [x] Daemon start launches `channel-daemon.js` instead of using a hidden CLI child option.
- [x] Dev-mode daemon start launches `channel-daemon.ts` through `ts-node`.
- [x] `channel stop` delegates to daemon stop and prints useful output.
- [x] `channel start --daemon` prints the daemon log path.
- [x] `channel status` shows the daemon log path for a running bridge.

## Integration Tests

- [x] CLI command parser accepts `channel start <name> --agent <name> --daemon`.
- [x] CLI command parser accepts `channel stop`.

## End-to-End Tests

- Manual later: connect a Telegram channel, start daemon bridge, send a message, stop daemon, confirm no further messages are processed.

## Test Data

- Temporary daemon state directory.
- Mock child process spawn result.
- Mock PID liveness and kill behavior.

## Test Reporting & Coverage

- Run targeted CLI tests after implementation.
- Run `npx ai-devkit@latest lint --feature channel-daemon-mode`.

Targeted evidence:
- `npx jest src/__tests__/commands/channel.test.ts src/__tests__/services/channel/channel.service.test.ts --runInBand`: 35 passed.
- `npm run test --workspace packages/cli -- --runInBand`: 35 suites passed, 559 tests passed.
- `npm run build`: 4 projects built successfully.

## Manual Testing

- Optional manual Telegram smoke test requires a real bot token and a running agent.

## Performance Testing

- Not required for v1; daemon lifecycle operations are local process and file operations.

## Bug Tracking

- Regressions should be covered by focused Jest tests before broader CLI test execution.
Loading
Loading