@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.
- package/CHANGELOG.md +20 -0
- package/index.ts +78 -10
- 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
|
-
|
|
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 ?? "" })),
|
|
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
|
|