Skip to content
Closed
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
70 changes: 70 additions & 0 deletions docs/ai/design/2026-08-14-feature-console-in-process-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
phase: design
title: Console In-Process Actions Design
description: Shared application-service boundary for Commander and the agent console
---

# Console In-Process Actions Design

## Architecture

```mermaid
flowchart LR
Commander[Commander handlers] --> AgentPackage[@ai-devkit/agent-manager services]
Commander --> ChannelPackage[@ai-devkit/channel-connector services]
Console[Console hooks and shell] --> Runner[In-process console action runner]
Runner --> AgentPackage
Runner --> ChannelPackage
AgentPackage --> AgentManager[Agent manager / terminal / tmux / registry]
ChannelPackage --> Channel[ConfigStore / bridge registry]
Channel --> Daemon[Detached channel daemon]
Commander --> CliUI[CLI reporter + exit adapter]
Console --> Pending[Immediate pending state + duplicate guard]
```

Commander remains responsible for parsing command-only input such as stdin, debug flags, interactive selection, CLI rendering, and applying an exit directive. Application services live in the packages that own their dependencies: agent lifecycle and terminal operations in `@ai-devkit/agent-manager`, and channel configuration/bridge process state in `@ai-devkit/channel-connector`. The console imports those public package services directly. The CLI alone resolves its source/build-specific channel-daemon entrypoint and passes that launch descriptor into the channel service.

## Service Contract

```ts
interface ApplicationActionResult {
ok: boolean;
message?: string;
cliExitCode?: number;
}
```

Services accept explicit dependencies (manager, focus manager, registry, tmux manager, config store, bridge service, reporter, and optional interactive selector). Defaults construct package-native production dependencies. Tests inject boundary doubles. Package services must not import CLI UI, debug, path-layout, group-storage, or process-exit modules.

`cliExitCode` is independent from `ok`: existing open/kill lookup failures produce command errors without forcing exit 1, while invalid start/rename and typed start failures retain exit 1. Commander applies only the explicit directive. Console uses `ok` and `message` for inline feedback.

## Pending State

The console uses a synchronous keyed pending gate. `begin(key, label)` records pending before invoking the async service and rejects a duplicate key until the promise settles. State notifications drive transient UI text. Required labels are `Sending`, `Opening`, and `Stopping channel`; other actions retain their existing pane submitting/confirmation behavior while gaining duplicate protection.

Tests assert the notification and pending snapshot immediately after submission and before resolving a deferred promise. No elapsed-time assertion is used, so the test deterministically proves the UI acknowledgement occurs in the same call stack and therefore satisfies the 50 ms target independent of machine load.

## Compatibility and Security

- Services reuse `sendToAgent`, `startAgent`, `killAgent`, `TerminalFocusManager`, `AgentRegistry`, `TmuxManager`, `ConfigStore`, and `ChannelService` rather than reimplementing lower-level behavior.
- The reusable services and their lower-level operations are exported by their owning packages; `packages/cli` does not duplicate them.
- CLI reporters preserve exact existing output strings and spinner behavior.
- Commander retains stdin/debug/group/print/wait parsing and presentation paths; only the shared interactive-agent action path moves behind services.
- Channel start still launches the dedicated daemon with `spawn` inside `ChannelService`; console actions no longer reinvoke the whole CLI.
- No shell is introduced. User values remain ordinary method inputs and daemon argv elements.
- Config and registry access stays behind existing stores/services; channel secrets are never returned to the TUI.

## Alternatives Considered

- Reuse Commander handlers directly: rejected because they couple services to parsing, `process.exit`, prompts, and terminal output.
- Keep subprocesses behind a generic executor: rejected because it retains startup latency and duplicate orchestration.
- Create TUI-only direct implementations: rejected because it leaves two behavior sources and risks validation/security drift.
- Extract all command modes into one large service: rejected because group, print, stdin, and wait modes are not console actions; retaining them in thin command orchestration reduces scope while sharing the requested interactive paths.
- Keep reusable services under `packages/cli`: rejected because it makes the console consume a CLI-owned reimplementation and prevents other package consumers from using the same behavior.

## Risks

- Output or exit drift during extraction: keep command tests and add wrapper/service tests.
- React state closure allows rapid duplicate input: use a synchronous mutable gate, not state alone.
- Service result loses useful error text: reporter captures the first/most relevant error while services also return a message.
- Channel daemon launch path differs between source and build: retain the resolver in the CLI adapter and pass a structured launch descriptor into the package service.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
phase: implementation
title: Console In-Process Actions Implementation
description: Implementation log for shared action services
---

# Console In-Process Actions Implementation

## Status

Implementation, validation, and publication for review are complete. PR: https://github.com/codeaholicguy/ai-devkit/pull/160

## Intended Changes

- `@ai-devkit/agent-manager` exports the reusable start/open/send/kill/rename service and its existing lower-level operations. The CLI keeps only a dependency-composition adapter for prompts, groups, debug logging, and terminal reporting.
- `@ai-devkit/channel-connector` exports bridge registry and daemon start/stop services. The CLI keeps foreground execution and source/build daemon-entrypoint resolution as CLI-specific adapters.
- Commander resolves paths and message input, calls the service, and applies only an explicit service exit directive.
- `runAction.ts` dispatches all seven console actions directly to injectable service methods; it no longer imports `child_process`.
- `pendingAction.ts` provides synchronous action identity/label notification and a keyed in-flight gate shared by all console flows.
- `ConsoleApp` publishes pending labels through the existing transient message surface. Required labels are `Sending`, `Opening`, and `Stopping channel`.

## Decisions and Deviations

- All seven actions fit coherently in the shared boundary; no action migration was deferred.
- Group, print, and wait orchestration moved into the agent package service. Foreground-channel execution remains in the CLI adapter because it owns the long-running Commander process, while daemon start/stop is package-owned and shared with the console.
- The configured Vercel React best-practices skill was unavailable in the active skill catalog. The implementation follows existing hook extraction, stable setter, memoized executor, and synchronous mutable-gate patterns; no render-time side effects or timing assertions were added.
- The channel daemon remains an intentional detached child process. Only the per-action full CLI respawn was removed.
- Remaining compatibility modules under `packages/cli/src/services` are export-only shims; service behavior has one implementation in the owning packages. The console imports the package APIs directly.

## Validation Evidence

- Red: focused action tests failed with seven zero-call assertions against the subprocess runner; pending tests failed because the pending module/mapping did not exist.
- Green/refactor: `npm test --workspace packages/cli -- src/__tests__/commands/agent.test.ts src/__tests__/commands/channel.test.ts src/__tests__/tui/console/actions/runAction.test.ts src/__tests__/tui/console/actions/pendingAction.test.ts` — 4 files, 114 tests passed.
- Focused console actions/hooks: 5 files, 33 tests passed.
- Full CLI: `npm test --workspace packages/cli` — 82 files, 975 tests passed after rebasing onto `origin/main`.
- CLI lint: `npm run lint --workspace packages/cli` — exit 0, five existing warnings and no errors.
- CLI build: `npm run build --workspace packages/cli` — exit 0, 199 files compiled after rebase.
- Feature docs: `npx ai-devkit@latest lint --feature console-in-process-actions` — all checks passed.
- Output isolation: default console services receive a silent reporter; a red-to-green test proves CLI spinners/text cannot write into the Ink terminal.
- Owning packages: agent-manager 25 files/504 tests and channel-connector 8 files/105 tests passed; both package builds and lints passed.
35 changes: 35 additions & 0 deletions docs/ai/planning/2026-08-14-feature-console-in-process-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
phase: planning
title: Console In-Process Actions Plan
description: TDD task queue for shared console action services
---

# Console In-Process Actions Plan

## Task Queue

- [x] Add red tests for direct service dispatch for all seven console actions.
- [x] Add red deterministic tests for immediate pending notification and duplicate suppression.
- [x] Add red tests for successful results, service errors, and retry after settlement.
- [x] Extract agent action services and convert start/open/send/kill/rename Commander handlers to thin wrappers.
- [x] Extract channel start/stop action services and convert Commander handlers to thin wrappers.
- [x] Replace the console subprocess runner with direct in-process dispatch and injected defaults.
- [x] Wire immediate `Sending`, `Opening`, and `Stopping channel` feedback and pending guards into console flows.
- [x] Refactor after green and update implementation/testing documents.
- [x] Run focused tests, full CLI tests, CLI lint, CLI build, and feature-doc lint.
- [x] Review diff, commit conventionally, rebase on `origin/main`, revalidate, push, and open a PR to `main`.

## Package Ownership Revision

- [x] Add red package-level tests for exported agent and channel application services.
- [x] Move reusable agent lifecycle orchestration into `@ai-devkit/agent-manager`.
- [x] Move reusable channel bridge registry and daemon orchestration into `@ai-devkit/channel-connector`.
- [x] Remove CLI-owned service reimplementations and import package services from Commander and the console.
- [x] Re-run package tests/builds plus the full CLI validation matrix.
- [x] Commit and update PR #160.

## Scope Decision

All seven actions share the same dispatch/result boundary and are included. Command-only group, print, stdin, wait, and foreground-channel modes remain in Commander orchestration, using existing lower-level services, because the console does not invoke them.

Implementation refined this boundary by moving group, print, wait, and foreground-channel orchestration into the application services as well. Commander retains only parsing/input acquisition, output/exit adaptation, and registration.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
phase: requirements
title: Console In-Process Actions Requirements
description: Remove per-action CLI subprocesses from the agent console
---

# Console In-Process Actions Requirements

## Problem Statement

`packages/cli/src/tui/console/actions/runAction.ts` starts a new CLI process for every console action. Startup and module-loading latency delays feedback for send, open, start, kill, rename, channel start, and channel stop, while duplicating orchestration between Commander and the TUI.

## Goals

- Invoke reusable application services directly from both Commander handlers and the console.
- Cover send, open, start, kill, rename, channel start, and channel stop in one coherent boundary.
- Preserve existing command output, exit behavior, validation, dependency/security boundaries, and test seams.
- Show `Sending`, `Opening`, and `Stopping channel` immediately when those actions begin.
- Suppress duplicate submission of an action while that action is pending.
- Keep acknowledgement under the 50 ms target by making the state transition synchronous and testing it without wall-clock timing.
- Add tests before production changes for direct invocation, immediate feedback, duplicate suppression, success, and errors.

## Non-Goals

- Removing the channel daemon child process; the daemon is the long-lived workload and remains intentionally detached.
- Changing command syntax, output wording, terminal resolution, tmux behavior, registry formats, or channel authorization/configuration.
- Adding new console actions or redesigning the console UI.

## Acceptance Criteria

- Console action execution no longer imports or calls `child_process.spawn` to reinvoke the CLI.
- Commander actions are thin adapters over the same application services used by the console.
- User-controlled values remain structured arguments/data and are never interpolated into a shell command.
- Existing CLI behavior and focused command tests remain green.
- Pending feedback is observable synchronously before the action promise settles.
- A second submission with the same pending key is ignored until settlement; retry is possible afterward.
- Focused tests, full CLI tests, CLI lint, and CLI build pass.

## Assumptions

- The user-approved objective is the authoritative requirements source for this feature.
- Existing feature documents for agent console start/kill/rename/channel and agent send define compatibility behavior.
- The configured Vercel React best-practices skill is unavailable in this runtime; repository React conventions and deterministic state tests are used instead.
43 changes: 43 additions & 0 deletions docs/ai/testing/2026-08-14-feature-console-in-process-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
phase: testing
title: Console In-Process Actions Testing
description: TDD coverage and validation evidence
---

# Console In-Process Actions Testing

## Required Scenarios

- [x] Each console action invokes its injected application service directly.
- [x] No console action runner starts a fresh CLI process.
- [x] Direct services cannot write CLI reporter output into the Ink terminal.
- [x] Pending feedback is emitted synchronously before a deferred action settles.
- [x] Sending renders `Sending`; opening renders `Opening`; channel stop renders `Stopping channel`.
- [x] Duplicate submission for a pending action is suppressed.
- [x] Submission can retry after success or error settlement.
- [x] Successful actions preserve existing console feedback and refresh behavior.
- [x] Service errors preserve useful messages and existing CLI output/exit behavior.
- [x] User-controlled values remain structured and reach the expected service dependency.

## Validation Commands

- Focused action/service/pending tests.
- Package service tests, lint, and builds for `agent-manager` and `channel-connector`.
- `npm test --workspace packages/cli`
- `npm run lint --workspace packages/cli`
- `npm run build --workspace packages/cli`
- `npx ai-devkit@latest lint --feature console-in-process-actions`

## Evidence

- TDD red run: 2 files failed; all seven direct-dispatch assertions observed zero service calls, and pending behavior was absent.
- Pending/action unit tests: 2 files, 20 tests passed.
- Focused command/action regression: 4 files, 114 tests passed.
- Focused console action/hook regression: 5 files, 33 tests passed.
- Full CLI suite: 82 files, 975 tests passed after rebase, exit 0.
- CLI lint: exit 0 with five pre-existing warnings, zero errors.
- CLI build: exit 0; SWC compiled 199 files after rebase and declaration generation completed.
- Feature-doc lint: exit 0; all required feature documents and worktree checks passed.
- Package boundary tests: agent open and channel daemon start invoke injected package dependencies directly.
- Package builds and lint: both owning packages compile and lint successfully.
- Agent-manager suite: 25 files and 504 tests passed with process inspection enabled; channel-connector suite: 8 files and 105 tests passed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, it, vi } from 'vitest';
import { AgentStatus } from '../../adapters/AgentAdapter.js';
import { createAgentActionService } from '../../services/AgentActionService.js';

describe('AgentActionService', () => {
it('owns terminal opening in the agent-manager package', async () => {
const agent = {
name: 'jarvis',
pid: 42,
status: AgentStatus.WAITING,
projectPath: '/tmp/project',
lastActive: new Date(),
type: 'codex' as const,
};
const focusManager = {
findTerminal: vi.fn().mockResolvedValue({ type: 'tmux', identifier: 'jarvis' }),
focusTerminal: vi.fn().mockResolvedValue(true),
};
const service = createAgentActionService({
manager: {
listAgents: vi.fn().mockResolvedValue([agent]),
resolveAgent: vi.fn().mockReturnValue(agent),
getAdapter: vi.fn(),
},
createFocusManager: () => focusManager,
});

await expect(service.open({ agentName: 'jarvis' })).resolves.toMatchObject({ ok: true });
expect(focusManager.findTerminal).toHaveBeenCalledWith(42);
expect(focusManager.focusTerminal).toHaveBeenCalledOnce();
});
});
43 changes: 43 additions & 0 deletions packages/agent-manager/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,46 @@ export type {
ClaudePrintAgentServiceOptions,
ClaudePrintSendResult,
} from './print/ClaudePrintAgentService.js';

export {
actionFailed,
actionSucceeded,
} from './services/ActionResult.js';
export type { ApplicationActionResult } from './services/ActionResult.js';
export {
createAgentActionService,
createAgentManager,
} from './services/AgentActionService.js';
export type {
AgentActionReporter,
AgentActionService,
AgentActionServiceDependencies,
KillAgentActionInput,
OpenAgentActionInput,
RenameAgentActionInput,
SendAgentActionInput,
StartAgentActionInput,
} from './services/AgentActionService.js';
export {
AgentNameInUseError,
AgentPidPollTimeoutError,
DEFAULT_PID_POLL_INTERVAL_MS,
DEFAULT_PID_POLL_TIMEOUT_MS,
TmuxUnavailableError,
assertSendTargetOptions,
killAgent,
sendToAgent,
sendToAgentGroup,
startAgent,
waitForAgentResponse,
} from './services/AgentService.js';
export type {
AgentGroup,
AgentSendWaitOptions,
AgentSendWaitResult,
AgentSendWaitTarget,
SendReporter,
SendToAgentGroupOptions,
SendToAgentOptions,
WaitForAgentResponseParams,
} from './services/AgentService.js';
12 changes: 12 additions & 0 deletions packages/agent-manager/src/services/ActionResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface ApplicationActionResult {
ok: boolean;
message?: string;
cliExitCode?: number;
}

export const actionSucceeded = (): ApplicationActionResult => ({ ok: true });

export const actionFailed = (
message: string,
cliExitCode?: number,
): ApplicationActionResult => ({ ok: false, message, cliExitCode });
Loading
Loading