Skip to content

feat(agent-core): add Monitor tool for real-time per-line stdout streaming#987

Open
Nitjsefnie wants to merge 9 commits into
MoonshotAI:mainfrom
Nitjsefnie:feat/monitor-tool
Open

feat(agent-core): add Monitor tool for real-time per-line stdout streaming#987
Nitjsefnie wants to merge 9 commits into
MoonshotAI:mainfrom
Nitjsefnie:feat/monitor-tool

Conversation

@Nitjsefnie

Copy link
Copy Markdown

Related Issue

Relates to #301 (which requested Monitor, Loop/Cron, and /goal). That issue was closed as completed — Cron and /goal exist, and the close note pointed to the existing background-task / /tasks flow for Monitor. But that flow is poll-based and notifies once on terminal state; the per-line streaming primitive #301 asked for ("启动后台进程持续监控日志/文件变化,实时推送到对话中" / "monitor tail -f deploy.log | grep ERROR") was never built. This PR adds it. Happy to reopen #301 or file a fresh tracking issue if maintainers prefer.

Problem

There is no way for the agent to watch a long-running command and react to its output as it happens. Bash+run_in_background plus TaskOutput is poll-based: the agent only learns about output when it explicitly checks, and the background subsystem only pushes a notification once, on terminal state. For tailing logs, watching a dev server, or polling job state, you want one notification per matching stdout line, in real time — Claude Code's Monitor tool. kimi-code had no equivalent.

What changed

A new monitor background-task kind that streams stdout incrementally and emits batched, volume-capped per-line notifications — reusing kimi-code's existing machinery rather than bolting on a parallel system:

  • MonitorBackgroundTask (agent/background/monitor-task.ts) observes the process streams exactly like ProcessBackgroundTask (tees every chunk to the sink so TaskOutput/persistence keep working) and additionally runs a line buffer: split on \n, hold a trailing partial line until its newline, flush the partial when the task ends (so a crash mid-line or printf with no trailing newline isn't dropped), batch lines within a 200 ms window, and auto-stop with a warning if it emits > 200 lines / 5 s (since turn.steer isn't rate-limited).
  • BackgroundManager.registerMonitorTask / monitorNotify render a <notification type="monitor_line"> via the existing renderNotificationXml and inject it with agent.turn.steer, reusing the same origin/hook path as terminal notifications. The terminal notification still fires on exit, so a crash/non-zero/timeout is always reported even if the filter matched nothing.
  • MonitorTool (tools/background/monitor.ts) mirrors BashTool (spawn, approval rule, non-interactive env, Windows path handling). Params: command, description, timeout_ms (default 5 min, ignored when persistent), persistent. Root/default profile only; goes through the normal approval + plan-mode policy. Stop via the existing TaskStop.
  • The monitor kind is wired through the closed BackgroundTaskInfo union, the protocol discriminated-union schema + event types, and the exhaustive mapKind switch — so tsc enforces completeness.

Why this approach fits kimi-code: it adds one task kind and one tool, reuses the worker/sink/notification/approval infrastructure verbatim, and the command self-filters (grep --line-buffered) so the existing output path is the event stream. Design doc (architecture, data flow, risks): https://docs.nitjsefni.eu/#/d/kimi-code/monitor-tool-design

Tests: monitor-task.test.ts (batching, partial-line hold, terminal flush of both a trailing partial and a single unterminated line, volume-cap auto-stop), monitor-manager.test.ts (per-line steer, terminal notification still fires, TaskOutput returns tee'd output), monitor-tool.test.ts (registration). pnpm typecheck, pnpm lint, pnpm test all green.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

🤖 Generated with Claude Code

Nitjsefnie and others added 8 commits June 22, 2026 22:02
Co-Authored-By: Kimi K2.7 Code <noreply@kimi.com>
…lume cap

Co-Authored-By: Kimi K2.7 Code <noreply@kimi.com>
…anager

Co-Authored-By: Kimi K2.7 Code <noreply@kimi.com>
Co-Authored-By: Kimi K2.7 Code <noreply@kimi.com>
Co-Authored-By: Kimi K2.7 Code <noreply@kimi.com>
Co-Authored-By: Kimi K2.7 Code <noreply@kimi.com>
… param

Widen BackgroundTaskNotification.source_kind to 'background_task' | 'monitor'
so monitorNotify builds a properly-typed notification and fires the hook
without an 'as unknown as' cast. Also drop the now-unused parameter on
MonitorBackgroundTask.checkVolume.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
agent-core and protocol are private (ignored) packages per
.changeset/README.md; the changeset must declare the bump on the
publishable CLI package that ships the feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: acc7571

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@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: 1e6cbfd1a8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +74 to +77
const taskId = this.background.registerMonitorTask(proc, command, args.description, {
detached: true,
timeoutMs,
});

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 Clean up the spawned monitor when registration fails

When a monitor command is spawned successfully but registerMonitorTask() throws, for example because background.maxRunningTasks has already been reached, this path lets the exception escape without killing or disposing proc. That leaves a long-running command such as tail -F ... orphaned outside the background manager, unlike the Bash path which cleans up after registration failures.

Useful? React with 👍 / 👎.

Comment on lines +60 to +62
case 'monitor':
// Monitors are tool-spawned persistent background tasks.
return 'tool';

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 Return monitor kind from the task adapter

For Monitor tasks exposed through the server task API, toProtocolTask() still maps the new agent-core monitor kind to tool, even though packages/protocol/src/task.ts now adds monitor as a valid wire kind. As a result, REST clients listing or fetching tasks cannot distinguish Monitor tasks from generic tool/question tasks and the newly added protocol literal is never emitted.

Useful? React with 👍 / 👎.

…emit 'monitor' wire kind

Address Codex review on MoonshotAI#987:
- MonitorTool now kills+disposes the spawned process if registerMonitorTask
  throws (e.g. maxRunningTasks reached), mirroring the Bash path, so a
  long-running command isn't orphaned outside the manager.
- mapKind returns the first-class 'monitor' wire kind (added to the protocol
  enum) instead of collapsing to 'tool', so REST clients can distinguish
  Monitor tasks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Nitjsefnie

Copy link
Copy Markdown
Author

Thanks @chatgpt-codex-connector — both addressed in acc7571:

  1. Orphaned process on registration failureMonitorTool now wraps registerMonitorTask in try/catch and kills+disposes the spawned process if registration throws (e.g. maxRunningTasks reached), mirroring the Bash path.
  2. Monitor wire kindmapKind now returns the first-class 'monitor' literal (which this PR added to backgroundTaskKindSchema) instead of collapsing to 'tool', so REST clients can distinguish Monitor tasks. Full pnpm typecheck stays green across all packages.

pnpm typecheck / lint / test all green.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

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.

1 participant