@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.
Files changed (2) hide show
  1. package/dist/index.mjs +81 -5
  2. 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
- let agentActive = false;
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 (!agentActive || warnedMemThisTurn && warnedDiskThisTurn) return;
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 (!agentActive) return;
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
- agentActive = true;
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
- agentActive = false;
3286
+ gate.agentEnd();
3211
3287
  if (timer) {
3212
3288
  clearInterval(timer);
3213
3289
  timer = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skydiveai/pi-extensions",
3
- "version": "0.1.252-beta.5",
3
+ "version": "0.1.252-beta.6",
4
4
  "homepage": "https://skydive.com",
5
5
  "license": "MIT",
6
6
  "author": "Create, Inc.",