Skip to content

Commit dba4d39

Browse files
committed
feat: structured monitor mode with stream/poll-diff, event history, and hard cutover from monitorFilter
- Replace monitorFilter with structured monitor config (triggers, strategy, persistence, throttle, detector) - Add poll-diff strategy wrapping commands in recurring loop with per-interval sample diffing - Add in-memory monitor event history queryable via monitorEvents/monitorSessionId/limit/offset - Guard monitor event callback against emitting after monitor disposal - Retry-based monitor history cleanup to prevent leaks from premature one-shot timers - Reject legacy monitorFilter with hard cutover error message - Update README, skill docs, and changelog for v0.11.1
1 parent 4a9df1f commit dba4d39

13 files changed

Lines changed: 974 additions & 141 deletions

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22

33
All notable changes to the `pi-interactive-shell` extension will be documented in this file.
44

5-
## [Unreleased]
5+
## [0.11.1] - 2026-04-12
6+
7+
### Changed
8+
- Monitor event callback now guards against emitting after the monitor is disposed, preventing stale queued notifications from a dismissed session.
9+
- Poll-diff strategy now wraps the command in a recurring loop and diffs per-interval samples instead of accumulating full PTY output.
10+
- Monitor event history cleanup retries until referenced monitor/session/active entries are gone, preventing history leaks from one-shot timers firing too early.
11+
12+
### Fixed
13+
- Fixed `await` on already-resolved detector command promise (removed unnecessary `await` on non-async return).
614

715
## [0.11.0] - 2026-04-11
816

README.md

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ The `interactive-shell` skill is automatically symlinked to `~/.pi/agent/skills/
4141
| **Interactive** (default) | Yes — blocks until exit | Tool return value | Editors, REPLs, SSH — when you need the result now |
4242
| **Hands-free** | No | Poll with `sessionId` | Dev servers, builds — when you want to watch progress and send follow-up commands |
4343
| **Dispatch** | No | Notification on completion via `triggerTurn` | Delegating tasks to subagents — fire and forget |
44-
| **Monitor** | No | Notification when a line matches `monitorFilter` | Watchers, logs, tests — wake only when something specific happens |
44+
| **Monitor** | No | Notification on structured monitor trigger events | Watchers, logs, tests, and state checks — wake only when something specific happens |
4545

4646
**Interactive** — The overlay opens, user controls the session, agent waits for it to close. Use for editors (`vim`), database shells (`psql`), or any task where the agent needs the final result immediately.
4747

4848
**Hands-free** — The overlay opens but returns immediately. The agent polls periodically with `sessionId` to check status and get new output. Good for long-running builds or dev servers where you want to react mid-flight (send input, check logs, kill when ready).
4949

5050
**Dispatch** — Returns immediately. No polling. The agent gets woken up via `triggerTurn` only when the session completes (natural exit, timeout, quiet detection, or user kill). The notification includes a tail of the output. This is the default for delegating work to subagents. Add `background: true` to skip the overlay entirely.
5151

52-
**Monitor** — Returns immediately. No polling, no completion notification. The agent gets woken up only when a cleaned output line matches `monitorFilter`. Use for watching logs or test output for specific events (`FAIL`, `error`, `Compiled successfully`). Runs headless; attach to inspect if needed.
52+
**Monitor** — Returns immediately. No polling, no completion notification. The agent gets woken up when a configured monitor trigger emits an event. Supports stream triggers and poll-diff checks, optional cooldowns, persistence controls, detector commands, and event history queries. Runs headless; attach to inspect if needed.
5353

5454
## Quick Start
5555

@@ -154,30 +154,44 @@ Multiple headless dispatches can run concurrently alongside a single interactive
154154

155155
### Monitor (Event-Driven)
156156

157-
Wake the agent immediately when output matches a pattern — no polling, no waiting for completion.
157+
These examples are **agent tool calls**. End users should ask in natural language (for example: "watch my tests and alert me on failures"), and Pi should invoke `interactive_shell` with the monitor config.
158+
159+
Wake the agent when monitor triggers emit events — no polling and no waiting for process completion.
158160

159161
```typescript
160-
// Watch tests for failures
162+
// Stream strategy with multiple named triggers
161163
interactive_shell({
162164
command: 'npm test --watch',
163165
mode: "monitor",
164-
monitorFilter: "FAIL"
166+
monitor: {
167+
strategy: "stream",
168+
triggers: [
169+
{ id: "failed", literal: "FAIL" },
170+
{ id: "error", regex: "/error|exception/i" }
171+
],
172+
throttle: { dedupeExactLine: true },
173+
persistence: { stopAfterFirstEvent: false }
174+
}
165175
})
166176

167-
// Watch dev server for errors (case-insensitive)
177+
// Poll-diff strategy (default 5s interval)
168178
interactive_shell({
169-
command: 'npm run dev',
179+
command: 'curl -sf http://localhost:3000/health',
170180
mode: "monitor",
171-
monitorFilter: "/error|warn/i"
181+
monitor: {
182+
strategy: "poll-diff",
183+
triggers: [{ id: "changed", regex: "/./" }],
184+
poll: { intervalMs: 5000 }
185+
}
172186
})
173187
```
174188

175-
Monitor mode is for **watching ongoing processes** where you care about specific output patterns, not completion. Unlike dispatch, you get woken up on the **first match**, not when the process exits. Use it for:
176-
- Test watchers — catch failures as they happen
177-
- Dev servers — alert on compile errors
178-
- Log tails — react to specific events
189+
Monitor mode emits structured payloads (`sessionId`, `eventId`, `timestamp`, `strategy`, `triggerId`, `matchedText`, `lineOrDiff`, `stream`) and can be queried later. `monitorFilter` was removed in favor of the structured `monitor` object.
179190

180-
`monitorFilter` accepts plain text (literal substring) or `/regex/flags`. ANSI escape codes are stripped before matching, so colors don't break your patterns. The notification includes the matched line and the matched text.
191+
```typescript
192+
interactive_shell({ monitorEvents: true, monitorSessionId: "calm-reef" })
193+
interactive_shell({ monitorEvents: true, monitorSessionId: "calm-reef", monitorEventLimit: 50, monitorEventOffset: 20 })
194+
```
181195

182196
Monitor sessions run headless and can be managed like other background sessions (`listBackground`, `/attach`, `dismissBackground`).
183197

@@ -302,7 +316,7 @@ interactive_shell({ dismissBackground: true }) // all sessions
302316
interactive_shell({ dismissBackground: "calm-reef" }) // specific session
303317
```
304318

305-
Monitor sessions work the same way — they're headless background sessions that happen to wake you on matches instead of completion.
319+
Monitor sessions work the same way — they're headless background sessions that wake you on monitor events instead of completion.
306320

307321
User can also `/spawn` to launch the configured default spawn agent, `/spawn codex`, `/spawn claude`, `/spawn pi`, `/spawn fork`, or `/spawn pi fork`. Add `--worktree` to spawn in a separate git worktree, for example `/spawn codex --worktree` or `/spawn pi fork --worktree`. Plain `/spawn claude` stays a normal interactive overlay. `fork` is Pi-only. Worktrees are left in place and the overlay will tell you where they were created. `/attach` or `/attach <id>` reattaches, and `/dismiss` or `/dismiss <id>` cleans up from the chat. The keyboard spawn shortcut is separate from `/spawn` and uses `spawn.shortcut`.
308322

examples/skills/interactive-shell/SKILL.md

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Pi has two ways to delegate work to other AI coding agents:
2424

2525
**Dispatch subagents** also use `interactive_shell` but with `mode: "dispatch"`. The agent fires the session and moves on. When the session completes, the agent is woken up via `triggerTurn` with the output in context. Add `background: true` for headless execution (no overlay).
2626

27-
**Monitor mode** (`mode: "monitor"`) runs headless and event-driven. It wakes the agent only when a cleaned output line matches `monitorFilter`, so there is no polling loop.
27+
**Monitor mode** (`mode: "monitor"`) runs headless and event-driven. It wakes the agent on structured monitor trigger events (stream or poll-diff), so there is no polling loop.
2828

2929
**Background subagents** run invisibly via the `subagent` tool. Pi-only, but captures full output and supports parallel execution.
3030

@@ -125,19 +125,30 @@ interactive_shell({
125125
```
126126

127127
### Monitor (Event-Driven, Headless)
128-
Run a background process and wake the agent only when output lines match `monitorFilter`.
128+
Run a background process and wake the agent on structured monitor triggers.
129129

130130
```typescript
131131
interactive_shell({
132132
command: 'npm test --watch',
133133
mode: "monitor",
134-
monitorFilter: "FAIL"
134+
monitor: {
135+
strategy: "stream",
136+
triggers: [
137+
{ id: "failed", literal: "FAIL" },
138+
{ id: "error", regex: "/error|warn/i" }
139+
],
140+
throttle: { dedupeExactLine: true }
141+
}
135142
})
136143

137144
interactive_shell({
138-
command: 'npm run dev',
145+
command: 'curl -sf http://localhost:3000/health',
139146
mode: "monitor",
140-
monitorFilter: "/error|warn/i"
147+
monitor: {
148+
strategy: "poll-diff",
149+
triggers: [{ id: "changed", regex: "/./" }],
150+
poll: { intervalMs: 5000 }
151+
}
141152
})
142153
```
143154

@@ -499,7 +510,11 @@ interactive_shell({ dismissBackground: true }) // all
499510
interactive_shell({ dismissBackground: "calm-reef" }) // specific
500511

501512
// Start an event-driven monitor session (headless)
502-
interactive_shell({ command: 'npm test --watch', mode: "monitor", monitorFilter: "FAIL" })
513+
interactive_shell({
514+
command: 'npm test --watch',
515+
mode: "monitor",
516+
monitor: { strategy: "stream", triggers: [{ id: "failed", literal: "FAIL" }] }
517+
})
503518
```
504519

505520
## Local Testing Hygiene
@@ -561,7 +576,11 @@ interactive_shell({
561576
interactive_shell({
562577
command: 'npm run dev',
563578
mode: "monitor",
564-
monitorFilter: "/error|warn/i",
579+
monitor: {
580+
strategy: "stream",
581+
triggers: [{ id: "warn", regex: "/error|warn/i" }],
582+
persistence: { stopAfterFirstEvent: false }
583+
},
565584
reason: "Wake me on server warnings"
566585
})
567586
```

0 commit comments

Comments
 (0)