@skydiveai/pi-extensions 0.1.252-beta.5 → 0.1.252-beta.6
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/dist/index.mjs +81 -5
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3028,6 +3028,13 @@ const bashDefaultTimeoutExtension = (pi) => {
|
|
|
3028
3028
|
* idle sandbox's resource usage is not the agent's problem and there is no
|
|
3029
3029
|
* open turn to deliver into anyway.
|
|
3030
3030
|
*
|
|
3031
|
+
* Delivery is further gated to turns that are ACTIVELY in tool-call work
|
|
3032
|
+
* (see WarningDeliveryGate): a warning that lands once the model has moved
|
|
3033
|
+
* on to its final reply triggers a continuation turn after the answer was
|
|
3034
|
+
* already produced, which breaks external-channel delivery (Slack steer-
|
|
3035
|
+
* after-reply). So the warning only interrupts between/around tool calls
|
|
3036
|
+
* and never chases a turn that is already wrapping up.
|
|
3037
|
+
*
|
|
3031
3038
|
* Best-effort throughout: any read failure (cgroup absent, controller not
|
|
3032
3039
|
* delegated, non-cgroup-v2 host, df missing) reads as "no signal" for that
|
|
3033
3040
|
* stat and the extension warns on what it can see — it must never break a
|
|
@@ -3151,20 +3158,77 @@ function resourcePressureWarningText(trigger, { mem, cpuPct, diskPct }) {
|
|
|
3151
3158
|
const closer = trigger === "memory" ? "If the workload genuinely needs more memory or CPU, request a bigger sandbox with `platform compute request`." : "A bigger compute tier will not add disk. Free space on the current computer instead, and do not request a compute upgrade for disk pressure.";
|
|
3152
3159
|
return `<system_notification>${lead} Current usage: ${stats.join(", ")}. Act now: ${remedy} ${closer} Do not mention this constraint as you work unless the user asks. This warning does not change whether the user is owed a reply. Do not call \`platform channel suppress-reply\` because of this warning. Complete any reply already owed after checkpointing. This is an automated resource warning, not a message from the user; continue the task, adjusted.</system_notification>`;
|
|
3153
3160
|
}
|
|
3161
|
+
/**
|
|
3162
|
+
* Whether a pressure warning may be delivered into the open turn right now.
|
|
3163
|
+
*
|
|
3164
|
+
* Only interrupts turns that are actively in tool-call work. The failure it
|
|
3165
|
+
* guards against: the poll lands after the model has stopped calling tools
|
|
3166
|
+
* and is streaming (or has finished) its final reply. `sendMessage` with
|
|
3167
|
+
* `triggerTurn: true` then starts a continuation turn BEHIND an answer the
|
|
3168
|
+
* user is already owed, which wedges external-channel delivery (Slack).
|
|
3169
|
+
*
|
|
3170
|
+
* Deliverable means ALL of:
|
|
3171
|
+
* - agent active (between agent_start and agent_end),
|
|
3172
|
+
* - the turn has issued at least one tool call,
|
|
3173
|
+
* - no assistant message is currently streaming (while one streams we cannot
|
|
3174
|
+
* know whether it will end the loop, so we hold and re-poll),
|
|
3175
|
+
* - the loop is not closed: no assistant message without tool calls has
|
|
3176
|
+
* completed this turn (that message IS the final reply; anything after it
|
|
3177
|
+
* is post-answer and must not be steered).
|
|
3178
|
+
*
|
|
3179
|
+
* Pure and exported so the gating is regression-testable without fake timers.
|
|
3180
|
+
*/
|
|
3181
|
+
var WarningDeliveryGate = class {
|
|
3182
|
+
agentActive = false;
|
|
3183
|
+
sawToolCall = false;
|
|
3184
|
+
toolsInFlight = 0;
|
|
3185
|
+
messageStreaming = false;
|
|
3186
|
+
loopClosed = false;
|
|
3187
|
+
agentStart() {
|
|
3188
|
+
this.agentActive = true;
|
|
3189
|
+
this.sawToolCall = false;
|
|
3190
|
+
this.toolsInFlight = 0;
|
|
3191
|
+
this.messageStreaming = false;
|
|
3192
|
+
this.loopClosed = false;
|
|
3193
|
+
}
|
|
3194
|
+
agentEnd() {
|
|
3195
|
+
this.agentActive = false;
|
|
3196
|
+
this.messageStreaming = false;
|
|
3197
|
+
}
|
|
3198
|
+
toolExecutionStart() {
|
|
3199
|
+
this.sawToolCall = true;
|
|
3200
|
+
this.toolsInFlight += 1;
|
|
3201
|
+
}
|
|
3202
|
+
toolExecutionEnd() {
|
|
3203
|
+
this.toolsInFlight = Math.max(0, this.toolsInFlight - 1);
|
|
3204
|
+
}
|
|
3205
|
+
messageStart(message) {
|
|
3206
|
+
if (message.role !== "assistant") return;
|
|
3207
|
+
this.messageStreaming = true;
|
|
3208
|
+
}
|
|
3209
|
+
messageEnd(message) {
|
|
3210
|
+
if (message.role !== "assistant") return;
|
|
3211
|
+
this.messageStreaming = false;
|
|
3212
|
+
if (!(Array.isArray(message.content) && message.content.some((part) => part.type === "toolCall"))) this.loopClosed = true;
|
|
3213
|
+
}
|
|
3214
|
+
canDeliver() {
|
|
3215
|
+
return this.agentActive && this.sawToolCall && !this.loopClosed && !this.messageStreaming;
|
|
3216
|
+
}
|
|
3217
|
+
};
|
|
3154
3218
|
const resourcePressureWarningExtension = (pi) => {
|
|
3155
|
-
|
|
3219
|
+
const gate = new WarningDeliveryGate();
|
|
3156
3220
|
let warnedMemThisTurn = false;
|
|
3157
3221
|
let warnedDiskThisTurn = false;
|
|
3158
3222
|
let timer = null;
|
|
3159
3223
|
const sampleCpu = createCpuSampler();
|
|
3160
3224
|
async function checkOnce() {
|
|
3161
|
-
if (!
|
|
3225
|
+
if (!gate.canDeliver() || warnedMemThisTurn && warnedDiskThisTurn) return;
|
|
3162
3226
|
const [mem, cpuPct, diskPct] = await Promise.all([
|
|
3163
3227
|
readMemUsePct(),
|
|
3164
3228
|
sampleCpu(),
|
|
3165
3229
|
readDiskUsePct()
|
|
3166
3230
|
]);
|
|
3167
|
-
if (!
|
|
3231
|
+
if (!gate.canDeliver()) return;
|
|
3168
3232
|
let trigger = null;
|
|
3169
3233
|
if (!warnedMemThisTurn && mem !== null && mem.pct >= 80) {
|
|
3170
3234
|
trigger = "memory";
|
|
@@ -3193,8 +3257,20 @@ const resourcePressureWarningExtension = (pi) => {
|
|
|
3193
3257
|
deliverAs: "followUp"
|
|
3194
3258
|
});
|
|
3195
3259
|
}
|
|
3260
|
+
pi.on("tool_execution_start", () => {
|
|
3261
|
+
gate.toolExecutionStart();
|
|
3262
|
+
});
|
|
3263
|
+
pi.on("tool_execution_end", () => {
|
|
3264
|
+
gate.toolExecutionEnd();
|
|
3265
|
+
});
|
|
3266
|
+
pi.on("message_start", (event) => {
|
|
3267
|
+
gate.messageStart(event.message);
|
|
3268
|
+
});
|
|
3269
|
+
pi.on("message_end", (event) => {
|
|
3270
|
+
gate.messageEnd(event.message);
|
|
3271
|
+
});
|
|
3196
3272
|
pi.on("agent_start", async () => {
|
|
3197
|
-
|
|
3273
|
+
gate.agentStart();
|
|
3198
3274
|
warnedMemThisTurn = false;
|
|
3199
3275
|
warnedDiskThisTurn = false;
|
|
3200
3276
|
if (!timer) {
|
|
@@ -3207,7 +3283,7 @@ const resourcePressureWarningExtension = (pi) => {
|
|
|
3207
3283
|
}
|
|
3208
3284
|
});
|
|
3209
3285
|
pi.on("agent_end", async () => {
|
|
3210
|
-
|
|
3286
|
+
gate.agentEnd();
|
|
3211
3287
|
if (timer) {
|
|
3212
3288
|
clearInterval(timer);
|
|
3213
3289
|
timer = null;
|