Fix input event streaming behavior semantics
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed extension input events to report `streamingBehavior` only for prompts actually queued during streaming ([#5107](https://github.com/earendil-works/pi-mono/pull/5107)).
|
||||
- Fixed fenced `diff` code blocks and other highlight.js scopes to keep theme-aware syntax colors after the `cli-highlight` replacement ([#5092](https://github.com/earendil-works/pi/issues/5092)).
|
||||
|
||||
## [0.76.0] - 2026-05-27
|
||||
|
||||
@@ -75,6 +75,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
||||
| `reload-runtime.ts` | Adds `/reload-runtime` and `reload_runtime` tool showing safe reload flow |
|
||||
| `interactive-shell.ts` | Run interactive commands (vim, htop) with full terminal via `user_bash` hook |
|
||||
| `inline-bash.ts` | Expands `!{command}` patterns in prompts via `input` event transformation |
|
||||
| `input-transform-streaming.ts` | Skips expensive input preprocessing for mid-stream steering via `streamingBehavior` |
|
||||
|
||||
### Git Integration
|
||||
|
||||
|
||||
@@ -984,7 +984,7 @@ export class AgentSession {
|
||||
currentText,
|
||||
currentImages,
|
||||
options?.source ?? "interactive",
|
||||
options?.streamingBehavior,
|
||||
this.isStreaming ? options?.streamingBehavior : undefined,
|
||||
);
|
||||
if (inputResult.action === "handled") {
|
||||
preflightResult?.(true);
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { fauxAssistantMessage, fauxToolCall, type Model } from "@earendil-works/pi-ai";
|
||||
import { Type } from "typebox";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { InputEvent } from "../../src/core/extensions/index.ts";
|
||||
import type { PromptTemplate } from "../../src/core/prompt-templates.ts";
|
||||
import { createSyntheticSourceInfo } from "../../src/core/source-info.ts";
|
||||
import { createTestResourceLoader } from "../utilities.ts";
|
||||
@@ -259,6 +260,80 @@ describe("AgentSession prompt characterization", () => {
|
||||
expect(getMessageText(harness.session.messages[0]!)).toBe("from extension");
|
||||
});
|
||||
|
||||
it("does not report streamingBehavior to input handlers while idle", async () => {
|
||||
const inputEvents: InputEvent[] = [];
|
||||
const harness = await createHarness({
|
||||
extensionFactories: [
|
||||
(pi) => {
|
||||
pi.on("input", (event) => {
|
||||
inputEvents.push(event);
|
||||
});
|
||||
},
|
||||
],
|
||||
});
|
||||
harnesses.push(harness);
|
||||
harness.setResponses([fauxAssistantMessage("ok")]);
|
||||
|
||||
await harness.session.prompt("idle", { streamingBehavior: "followUp" });
|
||||
|
||||
expect(inputEvents).toHaveLength(1);
|
||||
expect(inputEvents[0]?.streamingBehavior).toBeUndefined();
|
||||
});
|
||||
|
||||
it("reports streamingBehavior to input handlers while streaming", async () => {
|
||||
let releaseToolExecution: (() => void) | undefined;
|
||||
const toolRelease = new Promise<void>((resolve) => {
|
||||
releaseToolExecution = resolve;
|
||||
});
|
||||
const inputEvents: InputEvent[] = [];
|
||||
const waitTool: AgentTool = {
|
||||
name: "wait",
|
||||
label: "Wait",
|
||||
description: "Wait for release",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => {
|
||||
await toolRelease;
|
||||
return {
|
||||
content: [{ type: "text", text: "released" }],
|
||||
details: {},
|
||||
};
|
||||
},
|
||||
};
|
||||
const harness = await createHarness({
|
||||
tools: [waitTool],
|
||||
extensionFactories: [
|
||||
(pi) => {
|
||||
pi.on("input", (event) => {
|
||||
inputEvents.push(event);
|
||||
});
|
||||
},
|
||||
],
|
||||
});
|
||||
harnesses.push(harness);
|
||||
harness.setResponses([
|
||||
fauxAssistantMessage(fauxToolCall("wait", {}), { stopReason: "toolUse" }),
|
||||
fauxAssistantMessage("done"),
|
||||
]);
|
||||
|
||||
const sawToolStart = new Promise<void>((resolve) => {
|
||||
const unsubscribe = harness.session.subscribe((event) => {
|
||||
if (event.type === "tool_execution_start") {
|
||||
unsubscribe();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const promptPromise = harness.session.prompt("start");
|
||||
await sawToolStart;
|
||||
await harness.session.prompt("queued", { streamingBehavior: "followUp" });
|
||||
|
||||
expect(inputEvents.map((event) => event.streamingBehavior)).toEqual([undefined, "followUp"]);
|
||||
|
||||
releaseToolExecution?.();
|
||||
await promptPromise;
|
||||
});
|
||||
|
||||
it("throws when prompted during streaming without a streamingBehavior", async () => {
|
||||
let releaseToolExecution: (() => void) | undefined;
|
||||
const toolRelease = new Promise<void>((resolve) => {
|
||||
|
||||
Reference in New Issue
Block a user