feat(session): say when a session is watching its own background work

An agent that arms a monitor, backgrounds a shell or hands a task to a
cloud session is told to end its turn. The pane then falls quiet, Claude
Code's idle_prompt notification arrives a minute later, and every surface
files the session under NEEDS YOU with nothing for a human to answer.

Claude states what it is still running on the last row of its screen
(`⏵⏵ bypass permissions on · 1 monitor · ← for agents`). That row is now
`capabilities.workDetect.watchingLine` in the CLI registry, guarded by
compileVersionRegex() like every other config regex, and the idle probe
reads it off the capture it already takes: `watchingLabel()` in
session-activity.ts searches the last five lines only, so a session that
PRINTS "1 monitor" is not mistaken for one running it.

The label lands on Session.watching and rides toLightDetailedState() out
to every surface. The phone overview, the desktop home rail and the rich
sidebar rows wear it as a `watching` badge in the accent colour, beside
the state pill and never in place of it: an agent can arm a monitor and
ask a question in the same breath, and only the pill says which.

Verified end to end against a throwaway session on an isolated beta
instance: the payload carried `watching: "1 monitor"` once the turn
ended, the badge rendered next to a yellow `waiting` pill, and both
cleared when the monitor died.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michael Grundberg
2026-09-21 18:00:30 +02:00
co-authored by Claude Opus 5
parent 9466acfc1a
commit 3f2cde2db7
18 changed files with 590 additions and 5 deletions
+10
View File
@@ -308,6 +308,16 @@ const capabilitiesSchema = z
(src) => compileVersionRegex(src) !== null,
'workingLine must be a regex compileVersionRegex() accepts: at most 200 characters, no nested quantifiers'
),
// Same guard, same reasons: this one runs over the foot of a pane capture every
// time a session settles, and ~/.codeman/clis.json can set it.
watchingLine: z
.string()
.min(1)
.refine(
(src) => compileVersionRegex(src) !== null,
'watchingLine must be a regex compileVersionRegex() accepts: at most 200 characters, no nested quantifiers'
)
.optional(),
})
.strict()
.optional(),
+5
View File
@@ -206,6 +206,11 @@ const CLAUDE: CliEntry = {
workDetect: {
promptGlyph: '❯',
workingLine: String.raw`…\s*\((?:\d+h\s+)?(?:\d+m\s+)?\d+s\b|esc to interrupt`,
// Claude prints what it started in the background on the footer row beneath its
// composer, as `⏵⏵ bypass permissions on · 1 monitor · ← for agents`. The labels are the
// CLI's own words for each kind of background task, and group 1 is the one Codeman
// badges the session with. Verified against a live 2.1.278 pane on 2026-09-21.
watchingLine: String.raw`(\d+ (?:monitors?|shells?|teams?|local agents?|cloud sessions?|MCP tasks?|background tasks?|(?:background|remote) dynamic workflows?|Artifact comment monitors?))`,
},
requiresMux: false,
// Claude installs Codeman's own hooks block into every workspace it runs in, so its
+8
View File
@@ -344,6 +344,14 @@ export interface CliCapabilities {
promptGlyph: string;
/** Source of a regex matching the status line this CLI draws while a turn runs. */
workingLine: string;
/**
* Source of a regex matching the chip this CLI draws at the foot of its screen while
* work it started in the background is still running, e.g. Claude's `· 1 monitor ·`.
* Capture group 1 is the label Codeman shows, and the whole match stands in when the
* pattern declares no group. A CLI that omits this reports no background work, which
* is what every CLI did before the field existed.
*/
watchingLine?: string;
};
/** No direct-PTY fallback: the CLI must run inside tmux (secrets ride tmux setenv). */
requiresMux: boolean;