@davidorex/pi-behavior-monitors 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/index.ts +129 -11
- package/package.json +1 -1
- package/skills/pi-behavior-monitors/SKILL.md +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## v0.1.4
|
|
6
|
+
|
|
7
|
+
[compare changes](https://github.com/davidorex/pi-behavior-monitors/compare/v0.1.3...v0.1.4)
|
|
8
|
+
|
|
9
|
+
### 🚀 Enhancements
|
|
10
|
+
|
|
11
|
+
- Add ui.select menus to /monitors command for TUI discoverability ([4391ca3](https://github.com/davidorex/pi-behavior-monitors/commit/4391ca3))
|
|
12
|
+
|
|
13
|
+
### 📖 Documentation
|
|
14
|
+
|
|
15
|
+
- Update SKILL.md with buffered steer delivery and TUI autocomplete ([94aee6e](https://github.com/davidorex/pi-behavior-monitors/commit/94aee6e))
|
|
16
|
+
|
|
17
|
+
### ❤️ Contributors
|
|
18
|
+
|
|
19
|
+
- David Ryan <davidryan@gmail.com>
|
|
20
|
+
|
|
21
|
+
## v0.1.3
|
|
22
|
+
|
|
23
|
+
[compare changes](https://github.com/davidorex/pi-behavior-monitors/compare/v0.1.2...v0.1.3)
|
|
24
|
+
|
|
25
|
+
### 🩹 Fixes
|
|
26
|
+
|
|
27
|
+
- Buffer steer delivery at agent_end to work around pi async event queue ([c899fa5](https://github.com/davidorex/pi-behavior-monitors/commit/c899fa5))
|
|
28
|
+
|
|
29
|
+
### 📖 Documentation
|
|
30
|
+
|
|
31
|
+
- Add npm publish commands to CLAUDE.md ([91dbe87](https://github.com/davidorex/pi-behavior-monitors/commit/91dbe87))
|
|
32
|
+
|
|
33
|
+
### 🏡 Chore
|
|
34
|
+
|
|
35
|
+
- Scope package name to @davidorex/pi-behavior-monitors ([e9a9882](https://github.com/davidorex/pi-behavior-monitors/commit/e9a9882))
|
|
36
|
+
|
|
37
|
+
### ❤️ Contributors
|
|
38
|
+
|
|
39
|
+
- David Ryan <davidryan@gmail.com>
|
|
40
|
+
|
|
5
41
|
## v0.1.2
|
|
6
42
|
|
|
7
43
|
[compare changes](https://github.com/davidorex/pi-behavior-monitors/compare/v0.1.1...v0.1.2)
|
package/index.ts
CHANGED
|
@@ -121,6 +121,12 @@ export interface MonitorMessageDetails {
|
|
|
121
121
|
ceiling: number;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
interface BufferedSteer {
|
|
125
|
+
monitor: Monitor;
|
|
126
|
+
details: MonitorMessageDetails;
|
|
127
|
+
content: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
124
130
|
type MonitorEvent = "message_end" | "turn_end" | "agent_end" | "command";
|
|
125
131
|
|
|
126
132
|
const VALID_EVENTS = new Set<string>(["message_end", "turn_end", "agent_end", "command"]);
|
|
@@ -896,15 +902,21 @@ async function activate(
|
|
|
896
902
|
whileCount: monitor.whileCount + 1,
|
|
897
903
|
ceiling: monitor.ceiling,
|
|
898
904
|
};
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
details,
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
905
|
+
const content = `[${monitor.name}] ${description}${annotation}. ${action.steer}`;
|
|
906
|
+
|
|
907
|
+
if (monitor.event === "agent_end" || monitor.event === "command") {
|
|
908
|
+
// Already post-loop or command context: deliver immediately
|
|
909
|
+
pi.sendMessage<MonitorMessageDetails>(
|
|
910
|
+
{ customType: "monitor-steer", content, display: true, details },
|
|
911
|
+
{ deliverAs: "steer", triggerTurn: true },
|
|
912
|
+
);
|
|
913
|
+
} else {
|
|
914
|
+
// message_end / turn_end: buffer for drain at agent_end
|
|
915
|
+
// (pi's async event queue means these handlers run after the agent loop
|
|
916
|
+
// has already checked getSteeringMessages — direct sendMessage misses
|
|
917
|
+
// the window and the steer arrives one response late)
|
|
918
|
+
pendingAgentEndSteers.push({ monitor, details, content });
|
|
919
|
+
}
|
|
908
920
|
}
|
|
909
921
|
|
|
910
922
|
monitor.whileCount++;
|
|
@@ -1002,6 +1014,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1002
1014
|
m.activationCount = 0;
|
|
1003
1015
|
}
|
|
1004
1016
|
monitorsEnabled = true;
|
|
1017
|
+
pendingAgentEndSteers = [];
|
|
1005
1018
|
updateStatus();
|
|
1006
1019
|
});
|
|
1007
1020
|
|
|
@@ -1036,11 +1049,31 @@ export default function (pi: ExtensionAPI) {
|
|
|
1036
1049
|
return box;
|
|
1037
1050
|
});
|
|
1038
1051
|
|
|
1039
|
-
// --- abort support ---
|
|
1052
|
+
// --- abort support + buffered steer drain ---
|
|
1040
1053
|
pi.on("agent_end", async () => {
|
|
1041
1054
|
pi.events.emit("monitors:abort", undefined);
|
|
1055
|
+
|
|
1056
|
+
// Drain buffered steers from message_end/turn_end monitors.
|
|
1057
|
+
// The _agentEventQueue guarantees this runs AFTER all turn_end/message_end
|
|
1058
|
+
// handlers complete (sequential promise chain), so the buffer is populated.
|
|
1059
|
+
// Deliver only the first — the corrected response will re-trigger monitors
|
|
1060
|
+
// if additional issues remain.
|
|
1061
|
+
if (pendingAgentEndSteers.length > 0) {
|
|
1062
|
+
const first = pendingAgentEndSteers[0];
|
|
1063
|
+
pendingAgentEndSteers = [];
|
|
1064
|
+
pi.sendMessage<MonitorMessageDetails>(
|
|
1065
|
+
{ customType: "monitor-steer", content: first.content, display: true, details: first.details },
|
|
1066
|
+
{ deliverAs: "steer", triggerTurn: true },
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1042
1069
|
});
|
|
1043
1070
|
|
|
1071
|
+
// --- buffered steers for message_end/turn_end monitors ---
|
|
1072
|
+
// These monitors classify during the agent loop but can't inject steers in time
|
|
1073
|
+
// (pi's async event queue means extension handlers run after the agent loop checks
|
|
1074
|
+
// getSteeringMessages). Buffer steers here, drain at agent_end.
|
|
1075
|
+
let pendingAgentEndSteers: BufferedSteer[] = [];
|
|
1076
|
+
|
|
1044
1077
|
// --- per-turn exclusion tracking ---
|
|
1045
1078
|
let steeredThisTurn = new Set<string>();
|
|
1046
1079
|
pi.on("turn_start", () => { steeredThisTurn = new Set(); });
|
|
@@ -1094,8 +1127,43 @@ export default function (pi: ExtensionAPI) {
|
|
|
1094
1127
|
const monitorNames = new Set(monitors.map((m) => m.name));
|
|
1095
1128
|
const monitorsByName = new Map(monitors.map((m) => [m.name, m]));
|
|
1096
1129
|
|
|
1130
|
+
const monitorVerbs = ["rules", "patterns", "dismiss", "reset"];
|
|
1131
|
+
const rulesActions = ["add", "remove", "replace"];
|
|
1132
|
+
|
|
1097
1133
|
pi.registerCommand("monitors", {
|
|
1098
1134
|
description: "Manage behavior monitors",
|
|
1135
|
+
getArgumentCompletions(argumentPrefix: string) {
|
|
1136
|
+
const tokens = argumentPrefix.split(/\s+/);
|
|
1137
|
+
const last = tokens[tokens.length - 1];
|
|
1138
|
+
|
|
1139
|
+
// Level 0: no complete token yet — show global commands + monitor names
|
|
1140
|
+
if (tokens.length <= 1) {
|
|
1141
|
+
const items = [
|
|
1142
|
+
{ value: "on", label: "on", description: "Enable all monitoring" },
|
|
1143
|
+
{ value: "off", label: "off", description: "Pause all monitoring" },
|
|
1144
|
+
...Array.from(monitorNames).map((n) => ({ value: n, label: n, description: `${monitorsByName.get(n)?.description ?? ""} → rules|patterns|dismiss|reset` })),
|
|
1145
|
+
];
|
|
1146
|
+
return items.filter((i) => i.value.startsWith(last));
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const name = tokens[0];
|
|
1150
|
+
|
|
1151
|
+
// Level 1: monitor name entered — show verbs
|
|
1152
|
+
if (monitorNames.has(name) && tokens.length === 2) {
|
|
1153
|
+
return monitorVerbs
|
|
1154
|
+
.map((v) => ({ value: `${name} ${v}`, label: v, description: "" }))
|
|
1155
|
+
.filter((i) => i.label.startsWith(last));
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Level 2: monitor name + "rules" — show actions
|
|
1159
|
+
if (monitorNames.has(name) && tokens[1] === "rules" && tokens.length === 3) {
|
|
1160
|
+
return rulesActions
|
|
1161
|
+
.map((a) => ({ value: `${name} rules ${a}`, label: a, description: "" }))
|
|
1162
|
+
.filter((i) => i.label.startsWith(last));
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
return null;
|
|
1166
|
+
},
|
|
1099
1167
|
handler: async (args: string, ctx: ExtensionContext) => {
|
|
1100
1168
|
const cmd = parseMonitorsArgs(args, monitorNames);
|
|
1101
1169
|
|
|
@@ -1105,7 +1173,57 @@ export default function (pi: ExtensionAPI) {
|
|
|
1105
1173
|
}
|
|
1106
1174
|
|
|
1107
1175
|
if (cmd.type === "list") {
|
|
1108
|
-
|
|
1176
|
+
if (!ctx.hasUI) {
|
|
1177
|
+
handleList(monitors, ctx, monitorsEnabled);
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const options = [
|
|
1181
|
+
`on — Enable all monitoring`,
|
|
1182
|
+
`off — Pause all monitoring`,
|
|
1183
|
+
...monitors.map((m) => {
|
|
1184
|
+
const state = m.dismissed ? "dismissed" : m.whileCount > 0 ? `engaged (${m.whileCount}/${m.ceiling})` : "idle";
|
|
1185
|
+
return `${m.name} — ${m.description} [${state}]`;
|
|
1186
|
+
}),
|
|
1187
|
+
];
|
|
1188
|
+
const selected = await ctx.ui.select("Monitors", options);
|
|
1189
|
+
if (!selected) return;
|
|
1190
|
+
const selectedName = selected.split(" ")[0];
|
|
1191
|
+
if (selectedName === "on") {
|
|
1192
|
+
monitorsEnabled = true;
|
|
1193
|
+
updateStatus();
|
|
1194
|
+
ctx.ui.notify("Monitors enabled", "info");
|
|
1195
|
+
} else if (selectedName === "off") {
|
|
1196
|
+
monitorsEnabled = false;
|
|
1197
|
+
updateStatus();
|
|
1198
|
+
ctx.ui.notify("All monitors paused for this session", "info");
|
|
1199
|
+
} else {
|
|
1200
|
+
const monitor = monitorsByName.get(selectedName);
|
|
1201
|
+
if (!monitor) return;
|
|
1202
|
+
const verbOptions = [
|
|
1203
|
+
`inspect — Show monitor state and config`,
|
|
1204
|
+
`rules — List and manage rules`,
|
|
1205
|
+
`patterns — List known patterns`,
|
|
1206
|
+
`dismiss — Silence for this session`,
|
|
1207
|
+
`reset — Reset state and un-dismiss`,
|
|
1208
|
+
];
|
|
1209
|
+
const verb = await ctx.ui.select(`[${monitor.name}]`, verbOptions);
|
|
1210
|
+
if (!verb) return;
|
|
1211
|
+
const verbName = verb.split(" ")[0];
|
|
1212
|
+
if (verbName === "inspect") handleInspect(monitor, ctx);
|
|
1213
|
+
else if (verbName === "rules") handleRulesList(monitor, ctx);
|
|
1214
|
+
else if (verbName === "patterns") handlePatternsList(monitor, ctx);
|
|
1215
|
+
else if (verbName === "dismiss") {
|
|
1216
|
+
monitor.dismissed = true;
|
|
1217
|
+
monitor.whileCount = 0;
|
|
1218
|
+
updateStatus();
|
|
1219
|
+
ctx.ui.notify(`[${monitor.name}] Dismissed for this session`, "info");
|
|
1220
|
+
} else if (verbName === "reset") {
|
|
1221
|
+
monitor.dismissed = false;
|
|
1222
|
+
monitor.whileCount = 0;
|
|
1223
|
+
updateStatus();
|
|
1224
|
+
ctx.ui.notify(`[${monitor.name}] Reset`, "info");
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1109
1227
|
return;
|
|
1110
1228
|
}
|
|
1111
1229
|
|
package/package.json
CHANGED
|
@@ -270,6 +270,13 @@ the session. A `CLEAN` verdict resets the consecutive steer counter.
|
|
|
270
270
|
a turn, and monitor B has `"excludes": ["A"]`, monitor B skips that turn. Exclusion tracking
|
|
271
271
|
resets at `turn_start`.
|
|
272
272
|
|
|
273
|
+
**Buffered steer delivery**: Monitors on `message_end` or `turn_end` buffer their steer
|
|
274
|
+
messages and deliver them at `agent_end`. This is because pi's async event queue processes
|
|
275
|
+
extension handlers after the agent loop has already checked for steering messages. The
|
|
276
|
+
buffer is drained at `agent_end` — only the first buffered steer fires per agent run; the
|
|
277
|
+
corrected response re-triggers monitors naturally for any remaining issues. Monitors on
|
|
278
|
+
`agent_end` or `command` events deliver steers immediately (they already run post-loop).
|
|
279
|
+
|
|
273
280
|
**Abort**: Classification calls are aborted when the agent ends (via `agent_end` event).
|
|
274
281
|
Aborted classifications produce no verdict and no action.
|
|
275
282
|
|
|
@@ -280,7 +287,9 @@ the `id` field of array entries.
|
|
|
280
287
|
</runtime_behavior>
|
|
281
288
|
|
|
282
289
|
<commands>
|
|
283
|
-
All monitor management is through the `/monitors` command
|
|
290
|
+
All monitor management is through the `/monitors` command. Subcommands are
|
|
291
|
+
discoverable via pi's TUI autocomplete — typing `/monitors ` shows available
|
|
292
|
+
monitor names and global commands; selecting a monitor shows its verbs.
|
|
284
293
|
|
|
285
294
|
| Command | Description |
|
|
286
295
|
|---------|-------------|
|