@davidorex/pi-behavior-monitors 0.1.2 → 0.1.3

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/index.ts +78 -10
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## v0.1.3
6
+
7
+ [compare changes](https://github.com/davidorex/pi-behavior-monitors/compare/v0.1.2...v0.1.3)
8
+
9
+ ### 🩹 Fixes
10
+
11
+ - Buffer steer delivery at agent_end to work around pi async event queue ([c899fa5](https://github.com/davidorex/pi-behavior-monitors/commit/c899fa5))
12
+
13
+ ### 📖 Documentation
14
+
15
+ - Add npm publish commands to CLAUDE.md ([91dbe87](https://github.com/davidorex/pi-behavior-monitors/commit/91dbe87))
16
+
17
+ ### 🏡 Chore
18
+
19
+ - Scope package name to @davidorex/pi-behavior-monitors ([e9a9882](https://github.com/davidorex/pi-behavior-monitors/commit/e9a9882))
20
+
21
+ ### ❤️ Contributors
22
+
23
+ - David Ryan <davidryan@gmail.com>
24
+
5
25
  ## v0.1.2
6
26
 
7
27
  [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
- pi.sendMessage<MonitorMessageDetails>(
900
- {
901
- customType: "monitor-steer",
902
- content: `[${monitor.name}] ${description}${annotation}. ${action.steer}`,
903
- display: true,
904
- details,
905
- },
906
- { deliverAs: "steer", triggerTurn: true },
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 ?? "" })),
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davidorex/pi-behavior-monitors",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Behavior monitors for pi that watch agent activity and steer corrections",
5
5
  "type": "module",
6
6
  "keywords": [