Skip to content

feat: add Claude-oriented process tools (Bash, Monitor) on the existing extension-protocol surface #110

Description

@378-kaiabot

feat: add Claude-oriented process tools (Bash, Monitor) on the existing extension-protocol surface

Note

Automated check by Pi, model aperture/neuralwatt/kimi-k3-flex. Prepared against the current
codebase, aliou/pi-processes on branch main @ 0.12.0. See the local run log for the full investigation.

Supersedes #81.

Summary

Re-scopes and replaces #81 against main @ 0.12.0. The original issue assumed protocol plumbing that has since
landed: COMMAND_START, COMMAND_ADOPT, typed payloads, per-line log subscriptions, and a
notification pipeline with exactly the 20-per-minute throttle + suppression summary + completion
delivery the original design asked for. What remains is one small core change (notify config
through the start/adopt channels) plus the claude-processes extension itself.

Goal

Add a claude-processes Pi extension that provides Claude-oriented process tools while using the
existing pi-processes manager, logs, lifecycle handling, and /ps UI.

Initial tools:

  • Bash
  • Monitor

Future companions:

  • TaskStop
  • TaskOutput
  • TaskList

Current foundation (main @ 0.12.0)

What already exists, with the paths as of today:

  • Start/kill/clear protocol: CHANNELS.COMMAND_START, COMMAND_KILL, COMMAND_CLEAR in
    extensions/shared/protocol/channels.ts, with typed payloads in extensions/shared/protocol/commands.ts.
    CommandStartResult carries ProcessInfo, which already includes stdoutFile / stderrFile
    log paths (src/types.ts). Handlers live in extensions/processes/handlers/commands.ts.
  • Adopt protocol: COMMAND_ADOPT hands an externally spawned (detached, piped-stdio)
    ChildProcess to the manager along with pre-handover stdout/stderr.
    examples/bash-background.ts is a complete worked example of a bash tool override built on it
    (foreground run, background handoff on shortcut/timeout/2 min).
  • Typed client pattern: extensions/processes/client.ts (requestStart, requestKill with an
    absent-listener safety timeout, requestCombinedOutput, requestClear, requestPin).
    extensions/processes-logs/client.ts and extensions/processes-dock/client.ts show how a
    separate extension ships its own client over the same bus. claude-processes follows that
    pattern; it must not import src/manager or extensions/processes/* internals.
    (getManager() from the original issue no longer exists — core constructs the single
    ProcessManager in extensions/processes/index.ts.)
  • Per-line streaming: LOGS_SUBSCRIBE / LOGS_UNSUBSCRIBE / LOGS_CHUNK
    (extensions/shared/protocol/logs.ts, extensions/processes/handlers/subscriptions.ts) deliver
    an initial tail plus appended { type: "stdout" | "stderr", text } lines to a subscriberId,
    auto-removed on process end. The dock follow-overlay consumes this today.
  • Lifecycle + log-match notifications: extensions/processes/notifications/service.ts
    classifies ends and evaluates log matchers; extensions/processes/handlers/notifications.ts
    caps log_match delivery at 20 per 60 s window (shared across processes), flushing one
    log_match_suppressed summary at context attention per window. Semantics (forced display for
    crash/failure, intentional-stop bypass, attention defaults) are documented in
    docs/notifications.md.
  • Lifecycle broadcasts: STARTED / ENDED / OUTPUT_CHANGED on pi.events
    (extensions/shared/protocol/broadcasts.ts) for external listeners.

Gap analysis

One core change unlocks the rest:

Notify config through the protocol. Both command handlers call
notifications.register(info.id, {}), so every protocol-started process gets default lifecycle
notifications (success/failure → turn, killed → context). A foreground Bash tool that returns
its own result would produce a duplicate notification — the problem #81 solved with "process-owner
metadata." Today the same suppression is expressible as per-process attention ignore, but no
protocol channel accepts notify config for externally started processes; only the in-process
process tool does (tools/start, tools/update).

Proposed minimal change:

  1. Add optional notify to CommandStartPayload and CommandAdoptPayload, typed as a
    protocol-safe mirror of NotifyConfig in extensions/shared/protocol/.
  2. In handlers/commands.ts, normalize and register the payload's notify config instead of {},
    reusing the validation semantics in extensions/processes/tools/notify.ts (reject empty/
    whitespace-only patterns, validate regexes).
  3. Document in docs/notifications.md that ignore is force-downgraded to context for
    crash/failure, so a crashed foreground Bash still leaves a context-level trace.

Optionally add a source?: string field (e.g. "claude") to the start payload so future task
companion tools can filter their own processes out of the global list without owner semantics.

Design

claude-processes extension
  → pi.events protocol (CHANNELS from extensions/shared/protocol)
    → processes core extension (handlers in extensions/processes/handlers/*)
      → single ProcessManager (src/manager)
        → logs, /ps UI, lifecycle + log-match notifications

Bash

Start via COMMAND_START for both modes so the process is visible and controllable through /ps
from the start (the adopt-pattern keeps a foreground run invisible until handoff, which conflicts
with the /ps visibility requirement).

  • Foreground: subscribe to CHANNELS.ENDED filtered by id (with a reply-timeout safety net),
    read the result via REQUEST_COMBINED_OUTPUT, and suppress the core lifecycle notification via
    notify: { onSuccess: "ignore", onFailure: "ignore", onKilled: "ignore" } (failure/crash still
    lands as context — see gap analysis). Non-zero exit, timeout (own timer → COMMAND_KILL), and
    abort are tool errors containing the output tail.
  • Background: return immediately with the opaque id and the stdoutFile/stderrFile paths;
    completion arrives via the core lifecycle notification with default attention.
  • Accept dangerouslyDisableSandbox as a compatibility no-op.
  • Interplay: core's background-blocker hook (interception.blockBackgroundCommands) targets the
    built-in bash tool by name; decide and document what happens when claude-processes also
    registers a bash tool.

Monitor

Two viable builds; pick during implementation:

  • (a) Matcher-based (preferred): start with notify carrying
    logMatches: [{ pattern: ".", mode: "regex", repeat: true }]. Each stdout/stderr line becomes a
    log_match notification through the existing pipeline — inheriting the 20/minute cap, the
    suppression summary, classification, and persistence — with the completion delivered by the core
    lifecycle notification. Trade-off: notification wording is the generic core phrasing, not a
    "this is monitor output, not user input" frame.
  • (b) Subscription-based: LOGS_SUBSCRIBE and turn LOGS_CHUNK lines into custom
    notifications in the extension (full wording control, explicit "not user input" framing), at the
    cost of implementing a per-monitor throttle locally. Final completion handled by subscribing to
    CHANNELS.ENDED.

Persistent monitors run until stopped or session shutdown; non-persistent ones use an extension
side timer + COMMAND_KILL, which is classified as an intentional stop (attention per notify
config, never a spurious failure).

Task companions (future)

  • TaskList = REQUEST_LIST (+ source filter if added).
  • TaskOutput = REQUEST_COMBINED_OUTPUT / REQUEST_LOG_FILES.
  • TaskStop = COMMAND_KILL.
  • TaskWrite (stdin) has no protocol channel today — the process tool's write action is
    in-process only. Defer or add a COMMAND_WRITE channel as a separate change.

Constraints

  • One process manager per session; claude-processes never imports src/manager.
  • All Claude-oriented processes remain visible and controllable through /ps.
  • Session shutdown remains owned by the core extension (hooks/cleanup.ts).
  • Process IDs remain opaque.
  • No duplicate lifecycle messages for foreground Bash or Monitor (via notify config, not owner
    metadata).
  • New core-side user-visible strings go through the i18n layer (extensions/processes/i18n).

Work breakdown

  1. Add optional notify (+ optional source) to CommandStartPayload / CommandAdoptPayload;
    normalize in handlers/commands.ts; extend extensions/shared/protocol/ types.
  2. Create extensions/claude-processes/ with its own typed client (copy the requestStart /
    requestKill / timeout pattern from extensions/processes/client.ts); register it in
    package.json pi.extensions after the core extension.
  3. Implement Bash foreground and background modes.
  4. Implement Monitor (matcher-based first); decide (a) vs (b) with the wording trade-off
    documented.
  5. Add custom tool renderers while retaining /ps as the canonical process UI.
  6. Follow-ups: task companion tools; Anthropic-model-aware active-tool management that does not
    clobber a user's active-tool selection (nothing in the repo covers this today).

Tests

Unit (patterns in extensions/processes/handlers/commands.test.ts and
extensions/processes/client.test.ts):

  • start/adopt handlers normalize and register payload notify config;
  • protocol client absent-core reply timeout (pattern already in requestKill/requestPin);
  • foreground Bash success, failure, abort, and timeout;
  • background Bash immediate response and completion delivery;
  • Monitor line delivery via matcher path; intentional-stop classification for timeout kills.

E2E (tests/e2e/** with fixtures.ts):

  • Bash- and Monitor-started processes appear in /ps;
  • background completion and per-line Monitor notifications arrive, throttled per docs/notifications.md;
  • task stop works;
  • session shutdown kills all Claude-oriented processes.

Verification

pnpm typecheck
pnpm lint
pnpm test
pnpm test:e2e

Documentation

On landing, update README.md, the repo AGENTS.md (structure section — its src/ ... protocol
line is stale since the protocol moved to extensions/shared/protocol/), skills/pi-processes/SKILL.md,
docs/notifications.md (notify-via-protocol semantics), and add a Changeset.


Run host: solar-al-khwarizmi · Session: 01a04285-dfda-7dd4-887a-1154983d73d4 · Model: aperture/neuralwatt/kimi-k3-flex

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions