Skip to content

Group the agent commands under kcap agent - #383

Merged
alexeyzimarev merged 13 commits into
mainfrom
worktree-ancient-kindling-karp
Jul 29, 2026
Merged

alexeyzimarev merged 13 commits into
mainfrom
worktree-ancient-kindling-karp

Conversation

@alexeyzimarev

Copy link
Copy Markdown
Member

Closes #382
AI-1555

Regroups the three top-level agent commands into one kcap agent group, and adds a stop subcommand that did not exist before.

Command surface

kcap agent                                          # same as `kcap agent ls`
kcap agent start <vendor> [flags] [-- <agent args>]
kcap agent ls                    [--daemon <name>]
kcap agent attach <id>           [--daemon <name>]
kcap agent stop   <id>           [--daemon <name>]
kcap agent stop   --all [-y]     [--daemon <name>]

Breaking change

kcap run-agent, kcap attach, and kcap ls are removed, not aliased. Two flags are respelled: --name → --daemon (it always meant the daemon name, which reads as an agent name once there is a stop subcommand) and --detached → -d/--detach (matching kcap daemon start -d). The old spellings are rejected as unknown flags rather than silently accepted.

What else changed

  • kcap agent stop is new end to end: two append-only local-IPC frames (Stop = 8, StopAck = 70), a HandleLocalStopAsync on the daemon, and the CLI subcommand. It reuses the existing graceful path — /exit, 15s wait, then terminate.
  • A local stop works on --private agents, which nothing could stop before. HandleStopAgent’s body moved into StopAgentCoreAsync; the IsPrivate guard stays on the server-origin path (defence-in-depth against a leaked id) and only the 0600 local socket bypasses it. The core skips the two _server.* calls for private agents, since an unregistered agent has no server-side row.
  • Id prefixes: attach and stop accept any unique prefix; an ambiguous one lists the candidates. A full 32-hex id is sent verbatim so the daemon’s PID-record fallback can still reap survivors of a prior daemon incarnation.
  • kcap --help now lists these commands at all — previously all three were absent from help-usage.txt.

Verification

  • Unit suite: 42 failed / 4043 passed. Those 42 are a pre-existing baseline in CodexHookCommandTests and the uninstall/config.toml area — the merge base 7a4975a fails the identical 42. Zero new failures.
  • Integration suite: 144/144 passed.
  • dotnet publish -c Release clean of IL3050/IL2026.
  • kcap agent start/ls/stop/attach exercised live against a real daemon and the real claude CLI, including stop-by-prefix, --all with cancel/confirm/-y, unknown id, and the too-old-daemon path.

Known follow-ups

Design and plan

  • docs/superpowers/specs/2026-07-28-ai1555-agent-command-group-design.md
  • docs/superpowers/plans/2026-07-28-ai1555-agent-command-group.md

🤖 Generated with Claude Code

alexeyzimarev and others added 11 commits July 28, 2026 17:27
Regroups run-agent/attach/ls under `kcap agent start|ls|stop|attach`,
adds a local stop path, and renames --name to --daemon.

Refs AI-1555.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Also clarifies in the spec that a local stop falls back to the PID-record
reap before reporting an unknown agent.

Refs AI-1555.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…name

Comments and a user-visible overflow-detach message still referenced
the old kcap run-agent/attach/ls surface. Update them to kcap agent
start/attach/ls/stop so no stale command names remain in src/.
Threads the resolved daemon name through the version-skew hint so it
suggests `kcap daemon restart --force --name <name>` instead of a bare
restart the daemon would refuse while busy stopping agents. Distinguishes a
non-answering or erroring daemon from a genuinely empty agent list in
FetchAgentsAsync, so `stop --all` no longer silently no-ops when the daemon
never replies. Documents that `stop --all` affects every agent the daemon
hosts, not just ones started locally (#379). Lowercases full agent ids on
the pass-through path so uppercase ids resolve like their prefixes do, and
rejects a positional id combined with --all instead of ignoring the id.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Group agent lifecycle commands under kcap agent

✨ Enhancement 📝 Documentation 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Groups local agent lifecycle commands under discoverable kcap agent subcommands.
• Adds local IPC stopping for individual, all, and private daemon-hosted agents.
• Supports unique ID prefixes and documents intentional command and flag removals.
Diagram

sequenceDiagram
    actor User
    participant CLI as Agent CLI
    participant IPC as Local IPC
    participant Control as Control Socket
    participant Orch as Orchestrator
    participant Runtime as Agent Runtime
    participant Server as Kcap Server
    User->>CLI: agent stop id
    CLI->>IPC: Resolve prefix
    IPC->>Control: List then Stop
    Control->>Orch: Handle local stop
    Orch->>Runtime: Graceful exit
    Runtime-->>Orch: Exit or terminate
    opt Registered agent
        Orch-->>Server: Status and event
    end
    Orch-->>CLI: StopAck or Error
    CLI-->>User: Report result
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Retain deprecated top-level aliases
  • ➕ Reduces immediate disruption for existing scripts and user muscle memory.
  • ➕ Provides a migration window with deprecation warnings.
  • ➖ Continues occupying ambiguous top-level names such as ls and attach.
  • ➖ Creates two command surfaces that must remain documented and tested.
  • ➖ Conflicts with the explicit goal of establishing one coherent agent namespace.
2. Stop agents through the server API
  • ➕ Centralizes lifecycle authorization and auditing on the server.
  • ➕ Could apply flow-participant policy before terminating agents.
  • ➖ Cannot manage unregistered --private agents.
  • ➖ Adds network availability and latency to an inherently local operation.
  • ➖ Would not naturally support survivors known only through local PID records.

Recommendation: Keep the PR's grouped command router and 0600 local-socket stop path. It provides one discoverable lifecycle surface, preserves the private-agent security boundary, and reuses the established graceful shutdown and PID-record fallback; compatibility aliases would dilute the regrouping, while a server-only stop path cannot cover private agents.

Files changed (23) +2282 / -45

Enhancement (6) +432 / -9
FrameCodec.csEncode and decode local stop frames +4/-2

Encode and decode local stop frames

• Adds UTF-8 text payload handling for the new 'Stop' and 'StopAck' frame types.

src/Capacitor.Cli.Core/LocalIpc/FrameCodec.cs

FrameType.csDefine append-only stop protocol frame values +3/-1

Define append-only stop protocol frame values

• Adds client-to-daemon 'Stop = 8' and daemon-to-client 'StopAck = 70' values without renumbering existing frames.

src/Capacitor.Cli.Core/LocalIpc/FrameType.cs

LocalFrame.csAdd stop frame factories +2/-0

Add stop frame factories

• Provides constructors for stop requests and newline-delimited stop acknowledgements.

src/Capacitor.Cli.Core/LocalIpc/LocalFrame.cs

AgentOrchestrator.LocalIpc.csHandle local single-agent and stop-all requests +38/-5

Handle local single-agent and stop-all requests

• Adds local stop handling for live, private, and prior-daemon survivor agents. Stop-all operations run concurrently and return stopped IDs through 'StopAck'.

src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.LocalIpc.cs

LocalControlServer.csRoute stop frames to the orchestrator +2/-1

Route stop frames to the orchestrator

• Dispatches local 'Stop' frames to 'HandleLocalStopAsync' and includes Stop in unsupported-frame diagnostics.

src/Capacitor.Cli.Daemon/Services/LocalControlServer.cs

AgentCommand.csImplement the grouped agent command router +383/-0

Implement the grouped agent command router

• Routes start, list, attach, and stop operations, with bare 'agent' defaulting to list. Adds unique-prefix resolution, stop-all confirmation, robust list response validation, daemon targeting, and old-daemon guidance.

src/Capacitor.Cli/Commands/AgentCommand.cs

Refactor (3) +32 / -19
AgentStartArgs.csRename and revise agent start argument parsing +10/-10

Rename and revise agent start argument parsing

• Renames the parser from 'RunAgentArgs' to 'AgentStartArgs'. Accepts '--daemon' and '-d'/'--detach', while rejecting the removed '--name' and '--detached' spellings.

src/Capacitor.Cli.Core/AgentStartArgs.cs

AgentOrchestrator.csExtract authorization-neutral agent shutdown logic +20/-3

Extract authorization-neutral agent shutdown logic

• Moves graceful shutdown into 'StopAgentCoreAsync' for reuse by local requests. Server-origin requests still reject private agents, while private shutdowns skip server status and event calls.

src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.cs

Program.csReplace top-level agent commands with one route +2/-6

Replace top-level agent commands with one route

• Removes routing for 'run-agent', 'attach', and 'ls', and delegates 'agent' invocations to the new command group.

src/Capacitor.Cli/Program.cs

Tests (5) +268 / -1
AgentCommandRoutingTests.csTest agent subcommand routing +37/-0

Test agent subcommand routing

• Covers bare-agent list behavior, subcommand argument splitting, passthrough preservation, and the documented subcommand set.

test/Capacitor.Cli.Tests.Unit/AgentCommandRoutingTests.cs

AgentIdResolutionTests.csTest unique agent ID prefix resolution +66/-0

Test unique agent ID prefix resolution

• Covers unique, ambiguous, missing, and case-insensitive prefixes. Also verifies full hexadecimal IDs pass through and are normalized for ordinal daemon lookups.

test/Capacitor.Cli.Tests.Unit/AgentIdResolutionTests.cs

AgentOrchestratorLocalAttachTests.csTest daemon-side local agent stopping +64/-1

Test daemon-side local agent stopping

• Verifies private and registered agent stops, concurrent stop-all behavior, server reporting policy, and errors for unknown IDs.

test/Capacitor.Cli.Tests.Unit/AgentOrchestratorLocalAttachTests.cs

AgentStartArgsTests.csTest renamed agent start flags +80/-0

Test renamed agent start flags

• Covers vendor parsing, passthrough arguments, private and worktree modes, detach forms, daemon selection, and rejection of removed flag spellings.

test/Capacitor.Cli.Tests.Unit/AgentStartArgsTests.cs

FrameCodecTests.csTest stop protocol frame round trips +21/-0

Test stop protocol frame round trips

• Verifies single-agent and empty stop payloads plus newline-delimited 'StopAck' payloads survive codec round trips.

test/Capacitor.Cli.Tests.Unit/FrameCodecTests.cs

Documentation (9) +1550 / -16
README.mdDocument the grouped agent lifecycle commands +16/-11

Document the grouped agent lifecycle commands

• Replaces top-level command examples with 'kcap agent' usage. Documents stop, stop-all confirmation, unique ID prefixes, renamed flags, private agents, and the flow-participant limitation.

README.md

2026-07-28-ai1555-agent-command-group.mdAdd the agent command group implementation plan +1304/-0

Add the agent command group implementation plan

• Provides the task-by-task implementation and verification plan for CLI routing, identifier resolution, IPC frames, daemon stopping, tests, and documentation.

docs/superpowers/plans/2026-07-28-ai1555-agent-command-group.md

2026-07-28-ai1555-agent-command-group-design.mdDefine the agent command group design +170/-0

Define the agent command group design

• Records motivations, compatibility decisions, command semantics, wire protocol additions, authorization boundaries, version-skew handling, and known flow-participant limitations.

docs/superpowers/specs/2026-07-28-ai1555-agent-command-group-design.md

help-agent.txtAdd dedicated 'kcap agent' help +49/-0

Add dedicated 'kcap agent' help

• Documents all four subcommands, flags, ID-prefix behavior, private-agent handling, stop-all scope, and Unix-only support.

src/Capacitor.Cli.Core/Resources/help-agent.txt

help-usage.txtExpose agent commands in top-level help +6/-0

Expose agent commands in top-level help

• Adds an Agents section so start, list, attach, and stop are discoverable through 'kcap --help'.

src/Capacitor.Cli.Core/Resources/help-usage.txt

DaemonRunner.csRefresh local control socket terminology +1/-1

Refresh local control socket terminology

• Updates daemon wiring comments to reference the grouped 'kcap agent' command surface.

src/Capacitor.Cli.Daemon/DaemonRunner.cs

CodexLauncher.csUpdate Codex argument error guidance +1/-1

Update Codex argument error guidance

• Rewords the mandatory-argument conflict message to use 'kcap agent start codex'.

src/Capacitor.Cli.Daemon/Services/CodexLauncher.cs

IHostedAgentLauncher.csAlign launcher documentation with agent start +2/-2

Align launcher documentation with agent start

• Updates local-launch API comments and examples to reference the grouped command.

src/Capacitor.Cli.Daemon/Services/IHostedAgentLauncher.cs

IHostedAgentRuntime.csAlign runtime input documentation with agent attach +1/-1

Align runtime input documentation with agent attach

• Updates raw terminal input documentation to reference 'kcap agent attach'.

src/Capacitor.Cli.Daemon/Services/IHostedAgentRuntime.cs

@qodo-code-review

qodo-code-review Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Failed stops report success ✓ Resolved 🐞 Bug ≡ Correctness
Description
HandleLocalStopAsync emits StopAck after StopAgentCoreAsync, even though the core catches
every graceful-stop, cancellation, or termination failure and returns normally. The CLI consequently
prints Stopped <id>. when termination failed or did not establish that the process exited.
Code

src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.LocalIpc.cs[R30-32]

+        if (_agents.TryGetValue(agentId, out var agent)) {
+            await StopAgentCoreAsync(agent);
+            await FrameCodec.WriteAsync(stream, LocalFrame.StopAck(agentId), ct);
Evidence
The stop core sets status to completed and catches all exceptions around cancellation and
termination without communicating failure. The local handler then unconditionally sends StopAck,
and the client interprets that frame as success; the Unix termination implementation also returns
immediately after its final SIGKILL check without confirming subsequent exit.

src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.cs[1686-1748]
src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.LocalIpc.cs[21-44]
src/Capacitor.Cli/Commands/AgentCommand.cs[157-164]
src/Capacitor.Cli.Daemon/Pty/Unix/UnixPtyProcess.cs[229-248]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Local stop always returns `StopAck`, even when the underlying stop core logs and suppresses a termination failure. This falsely reports successful termination to users and automation.

## Issue Context
Return an explicit success/failure result from the stop core, verify process exit after termination, and emit an `Error` response when stopping cannot be confirmed. Handle partial failures for `--all` without claiming every snapshot entry stopped.

## Fix Focus Areas
- src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.cs[1686-1748]
- src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.LocalIpc.cs[21-44]
- src/Capacitor.Cli/Commands/AgentCommand.cs[148-187]
- test/Capacitor.Cli.Tests.Unit/AgentOrchestratorLocalAttachTests.cs[583-644]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Invalid flags target default ✓ Resolved 🐞 Bug ≡ Correctness
Description
The ls, attach, and stop paths do not validate their complete argument lists, so removed
flags, unknown flags, and a valueless --daemon silently fall back to the default daemon. For
example, kcap agent stop --all -y --daemon can stop every agent on the default daemon instead of
returning a usage error.
Code

src/Capacitor.Cli/Commands/AgentCommand.cs[R337-340]

+    static string? NameFrom(string[] args) {
+        var i = Array.IndexOf(args, "--daemon");
+
+        return i >= 0 && i + 1 < args.Length ? args[i + 1] : null;
Evidence
StopAsync recognizes --all and -y independently, then resolves the daemon through NameFrom;
NameFrom returns null for a trailing --daemon and ignores all other unsupported tokens.
ResolveName(null) delegates to DaemonNameResolver, whose documented fallback selects
environment/profile/user defaults, while that resolver explicitly rejects missing values in its
normal --name path to prevent destructive default targeting.

src/Capacitor.Cli/Commands/AgentCommand.cs[84-145]
src/Capacitor.Cli/Commands/AgentCommand.cs[331-341]
src/Capacitor.Cli.Core/DaemonNameResolver.cs[35-68]
test/Capacitor.Cli.Tests.Unit/AgentStartArgsTests.cs[73-79]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`agent ls`, `attach`, and `stop` only extract `--daemon` and silently ignore malformed or unsupported arguments. A missing `--daemon` value can therefore make destructive operations target the default daemon.

## Issue Context
Add strict parsers that reject unknown flags, removed spellings, extra positional arguments, duplicate incompatible options, and missing or flag-like `--daemon` values before resolving a socket.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/AgentCommand.cs[63-145]
- src/Capacitor.Cli/Commands/AgentCommand.cs[203-204]
- src/Capacitor.Cli/Commands/AgentCommand.cs[331-341]
- test/Capacitor.Cli.Tests.Unit/AgentCommandRoutingTests.cs[1-37]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Getting started remains unsynchronized 📘 Rule violation ⚙ Maintainability
Description
The PR replaces three top-level commands with the kcap agent group, but the README Getting started
section was not updated to introduce or link to the changed command surface. This fails the
checklist's explicit requirement to update both Getting started and the applicable command section.
Code

src/Capacitor.Cli/Program.cs[R277-278]

+    case "agent":
+        return await AgentCommand.HandleAsync(args);
Evidence
The routing change establishes the new kcap agent command surface, while the Getting started
section contains no kcap agent guidance or link despite the rule requiring that section to be
synchronized.

CLAUDE.md: Keep README Documentation Synchronized With User-Facing CLI Changes
src/Capacitor.Cli/Program.cs[274-278]
README.md[49-132]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `kcap agent` command surface is documented in the command reference but not in README.md's Getting started section.

## Issue Context
PR Compliance ID 7 explicitly requires user-facing CLI changes to update both the Getting started section and the applicable per-command section.

## Fix Focus Areas
- README.md[49-132]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. --yes missing from README ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
agent stop accepts the new --yes flag, but the applicable README section documents only -y.
Users relying on public documentation cannot discover the supported long-form flag.
Code

src/Capacitor.Cli/Commands/AgentCommand.cs[86]

+        var yes = args.Contains("--yes") || args.Contains("-y");
Evidence
The implementation explicitly recognizes --yes, while the README examples and explanatory text
mention only -y, violating the requirement to synchronize public documentation with user-facing
flags.

CLAUDE.md: Keep README Documentation Synchronized With User-Facing CLI Changes
src/Capacitor.Cli/Commands/AgentCommand.cs[84-86]
README.md[929-938]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `kcap agent stop --all` implementation accepts both `-y` and `--yes`, while README.md documents only `-y`.

## Issue Context
PR Compliance ID 7 requires every new or changed user-facing flag to be reflected in README.md, including the applicable per-command section.

## Fix Focus Areas
- README.md[929-938]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Confirmation omits stopped agents 🐞 Bug ≡ Correctness
Description
StopAsync confirms a client-side agent snapshot but sends an empty target that the daemon expands
into a new snapshot at execution time. An agent launched between the prompt and stop-frame handling
is therefore terminated without appearing in the confirmation list.
Code

src/Capacitor.Cli/Commands/AgentCommand.cs[137]

+            target = "";
Evidence
The client fetches and displays a snapshot, confirms its count, then discards those IDs and sends
the empty sentinel. The daemon handles that sentinel by taking a fresh _agents.Values snapshot, so
the confirmed and terminated sets are not guaranteed to match.

src/Capacitor.Cli/Commands/AgentCommand.cs[113-145]
src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.LocalIpc.cs[21-25]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `--all` confirmation covers one snapshot, while the empty stop sentinel operates on a later daemon-side snapshot. Concurrently launched agents can be stopped without being shown to the user.

## Issue Context
Bind execution to the exact IDs displayed during confirmation, such as by sending those IDs explicitly or introducing a versioned snapshot that the daemon validates atomically.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/AgentCommand.cs[113-145]
- src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.LocalIpc.cs[21-27]
- src/Capacitor.Cli.Core/LocalIpc/LocalFrame.cs[23-24]
- test/Capacitor.Cli.Tests.Unit/AgentOrchestratorLocalAttachTests.cs[620-633]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/Capacitor.Cli/Commands/AgentCommand.cs
Comment on lines +277 to +278
case "agent":
return await AgentCommand.HandleAsync(args);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Getting started remains unsynchronized 📘 Rule violation ⚙ Maintainability

The PR replaces three top-level commands with the kcap agent group, but the README Getting started
section was not updated to introduce or link to the changed command surface. This fails the
checklist's explicit requirement to update both Getting started and the applicable command section.
Agent Prompt
## Issue description
The new `kcap agent` command surface is documented in the command reference but not in README.md's Getting started section.

## Issue Context
PR Compliance ID 7 explicitly requires user-facing CLI changes to update both the Getting started section and the applicable per-command section.

## Fix Focus Areas
- README.md[49-132]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/Capacitor.Cli/Commands/AgentCommand.cs Outdated
Comment thread src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.LocalIpc.cs Outdated
}
}

target = "";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

5. Confirmation omits stopped agents 🐞 Bug ≡ Correctness

StopAsync confirms a client-side agent snapshot but sends an empty target that the daemon expands
into a new snapshot at execution time. An agent launched between the prompt and stop-frame handling
is therefore terminated without appearing in the confirmation list.
Agent Prompt
## Issue description
The `--all` confirmation covers one snapshot, while the empty stop sentinel operates on a later daemon-side snapshot. Concurrently launched agents can be stopped without being shown to the user.

## Issue Context
Bind execution to the exact IDs displayed during confirmation, such as by sending those IDs explicitly or introducing a versioned snapshot that the daemon validates atomically.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/AgentCommand.cs[113-145]
- src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.LocalIpc.cs[21-27]
- src/Capacitor.Cli.Core/LocalIpc/LocalFrame.cs[23-24]
- test/Capacitor.Cli.Tests.Unit/AgentOrchestratorLocalAttachTests.cs[620-633]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f4ad267c62

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +337 to +340
static string? NameFrom(string[] args) {
var i = Array.IndexOf(args, "--daemon");

return i >= 0 && i + 1 < args.Length ? args[i + 1] : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject a missing --daemon value before stopping

When --daemon is the final token, NameFrom returns null instead of reporting the missing value, so a command such as kcap agent stop --all -y --daemon silently resolves to the profile's default daemon and stops all of its agents without confirmation. Validate that --daemon has a non-flag value before resolving or issuing any stop.

Useful? React with 👍 / 👎.

}
}

target = "";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bind --all confirmation to the displayed agent set

In the interactive --all flow, the agent list is fetched before the prompt, but after confirmation target = "" tells the daemon to enumerate its current agents again. If an agent is launched from the web UI or a review flow while the user is considering the prompt, that new, undisclosed agent is also stopped even though it was absent from the displayed list and count; send the confirmed IDs or re-list and reconfirm when the set changes.

Useful? React with 👍 / 👎.

alexeyzimarev and others added 2 commits July 28, 2026 23:31
Closes agent-command-group defects found by automated review: a valueless
--daemon on ls/attach/stop could silently retarget a destructive stop at the
default daemon or crash on an uncaught ArgumentException; StopAck always
reported success even when the graceful stop failed or the process never
exited; and the README omitted the --yes long form already documented in
help-agent.txt.

- AgentCommand.DaemonNameFrom validates --daemon has a real value before any
  socket is touched, reporting a clean usage error instead of resolving the
  default daemon or crashing.
- StopAgentCoreAsync now returns whether the stop was actually confirmed
  (Runtime.HasExited); StopAck's payload is one id\tstatus line per agent so
  the CLI can report per-agent success/failure and exit 1 on any failure,
  matching `kcap daemon stop`.
… review

StopAgentCoreAsync read Runtime.HasExited immediately after TerminateAsync's
SIGKILL, but TerminateAsync's own post-kill waitpid is non-blocking and
usually hasn't reaped the child yet — so a hung agent that needed SIGKILL
(the main reason anyone runs `kcap agent stop`) was reported as `failed`
with no corresponding daemon-log error. Poll briefly (WaitForExitAsync) after
TerminateAsync before treating the stop as unconfirmed.

Also closes the same "--daemon/--name with an empty value" validation gap in
two more places: AgentCommand.DaemonNameFrom (ls/attach/stop) and
AgentStartArgs (agent start), both of which let `--daemon ""` or
`--daemon --flag` through to crash instead of printing a clean error.

Minor: collapse a redundant double-empty-check in SendStopAsync, and update
the FrameCodecTests name/payload, design doc, and README to match the
id\tstatus StopAck shape and the failure-reporting behavior it enables.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@alexeyzimarev

Copy link
Copy Markdown
Member Author

Triaged the bot reviews. Pushed 1c3c868 + 12e1a0a addressing three of the five findings; two are deferred with reasoning, and one is a false positive.

Fixed

Qodo #3 / Codex P1 — valueless --daemon retargets a destructive command. Confirmed and fixed. NameFrom returned null when --daemon was the final token, and ResolveName(null) resolves the profile default daemon — so kcap agent stop --all -y --daemon stopped every agent on the default daemon, with -y suppressing the confirmation that would have exposed it. A --daemon followed by a flag also crashed with an uncaught ArgumentException. Replaced with an internal static validating DaemonNameFrom that rejects both (and the empty string), checked in ls/attach/stop before any socket is resolved. AgentStartArgs.Parse had the same flag-as-value hole and now applies the identical rule, so the whole group agrees.

Qodo #4 — failed stops reported success. Confirmed and fixed. StopAgentCoreAsync now returns a success bool, StopAck’s payload is id\tstatus per line (stopped/failed), and the CLI prints per-agent results and exits 1 if any failed — matching kcap daemon stop’s existing exit-code behaviour. The frame is new in this unreleased PR, so the format change costs nothing.

Worth flagging: the first attempt at this fix was itself wrong, and review caught it. Reading HasExited immediately after TerminateAsync is not proof of death — UnixPtyProcess.TerminateAsync sends SIGKILL then issues a single non-blocking waitpid microseconds later, before the kernel reaps the child. A hung agent (the main reason anyone runs agent stop) would have been killed successfully and reported Failed to stop. 12e1a0a polls briefly via WaitForExitAsync before calling a stop failed, with a test double modelling the reap landing just after the kill — it fails against the previous commit and passes now.

Qodo #1 — --yes undocumented. Fixed in README.md, which now also documents the failure line and non-zero exit.

Deferred

Qodo #5 / Codex P2 — --all confirmation TOCTOU. Real: the client lists and prompts, then sends the empty sentinel and the daemon re-enumerates, so an agent launched during the prompt is stopped without appearing in the list. Binding execution to the confirmed ids changes the Stop frame’s payload contract, which I would rather not fold into this PR. Tracked alongside #379, which covers the related and larger problem that these commands cannot distinguish a flow participant from your own agent.

Not a defect

Qodo #2 — "Getting started remains unsynchronized". The README’s ## Getting started section covers install, setup, import, dashboard, and MCP servers; it has never introduced run-agent/attach/ls, so there is nothing there to re-point. The rule is to check both sections, which was done — the quick-start legitimately does not cover local agents, and the per-command section plus both cross-references were updated.

@alexeyzimarev
alexeyzimarev merged commit 6659557 into main Jul 29, 2026
6 checks passed
@alexeyzimarev
alexeyzimarev deleted the worktree-ancient-kindling-karp branch July 29, 2026 09:09
alexeyzimarev added a commit that referenced this pull request Jul 29, 2026
…ombstone (#392)

* Restore the `kcap agent` command group shadowed by the retired-verb tombstone

#390 added a `command is "agent"` guard at the top of Program.cs that exits 2
with a pointer to `kcap daemon`. #383 added the `kcap agent start|ls|stop|attach`
group at `case "agent":` in the switch below it. They merged without conflict —
different regions of the file — and the guard wins, so every `kcap agent`
subcommand on main answers "Unknown command: agent" and exits 2.

Neither side's tests caught it: #390's assert the tombstone fires (it does), and
#383's exercise the parsers and daemon handlers directly, never through
Program.cs. AgentVerbDispatchTests now spawns the real binary and pins that the
group is reachable, which is the gap that let this through.

The daemon signpost is kept where it doesn't collide: `kcap agent status`,
`restart`, `logs`, `doctor`, and `service` only ever meant the daemon, so they
answer with a pointer to `kcap daemon <verb>`. `start` and `stop` belong to both
groups and dispatch to the agent group.

Note this is reachable only with a server configured — the `agent` group resolves
config before dispatch, unlike the pre-config tombstone it replaces.

Closes #391
AI-1570
Reverts the CLI behaviour of #390 (AI-1569).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Remove the retired-verb text ban; fix dispatch, Windows and offline gaps

Per the decision to keep `kcap agent` as the coding-agent group, drops
RetiredVerbScanTests (AI-1567), which banned the literal string "kcap agent"
from all shipped text under kcap/, src/ and npm/ and is what failed CI on both
platforms.

Addresses the review findings on this PR:

- The dispatch test did not prove dispatch. In a config-less environment every
  non-help case exited 1 at the missing-server gate and `--help` returned from
  the global help path, so nothing pinned that AgentCommand was reached — another
  pre-dispatch guard would have passed it. Each case now asserts a string only
  AgentCommand emits, and points `ls` at a daemon name that cannot exist so the
  handler's own output is deterministic.

- Windows bypassed the daemon signpost. NotSupportedOnWindows returned before the
  subcommand switch, so `kcap agent status` reported that coding agents are
  unsupported instead of pointing at `kcap daemon status`, which Windows does
  support. The signpost now runs ahead of the platform guard.

- The group sat behind the server-config gate, which made the signpost
  unreachable offline. `agent` joins offlineCommands and `start` — the only
  subcommand that needs a server for the daemon to record to — reports the
  missing server itself. ls/attach/stop only ever talk to the local socket.

Two defects the rewritten test then caught in the group itself:

- Any argv[1] was treated as a subcommand, so `kcap agent --daemon dev` failed
  with "unknown subcommand '--daemon'" instead of listing that daemon's agents.
  A leading flag is now an `ls` option.

- `--no-update-check` is global and left in argv by Program.cs, so strict
  parsing read it as a subcommand and, worse, as a vendor name for `start`.
  Global flags are dropped before the split.

Closes #391
AI-1570
Reverts the CLI behaviour of #390 (AI-1569) and the text ban from #389 (AI-1567).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

Group the agent commands under kcap agent

1 participant