Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/app/src/features/chat/runtime/chat-queue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ describe("Chat pending prompt", () => {
expect(chat.store.getState().messages).toEqual([]);
});

it("returns to ready when Pi handles a command without starting a turn", async () => {
const { chat, transport, attach } = makeChat();
await attach({});
transport.promptStarted = false;

await chat.prompt("/mcp");

expect(chat.store.getState().messages).toEqual([]);
expect(chat.store.getState().status).toBe("ready");
});

it("keeps the queue empty when a follow-up races to a real prompt", async () => {
const { chat, transport, attach, live } = makeChat();
await attach({});
Expand Down
3 changes: 3 additions & 0 deletions apps/app/src/features/chat/runtime/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,9 @@ export class Chat {
});
if (!receipt.started) {
this.#state.messages = this.#state.messages.filter((message) => message.id !== messageId);
// No turn started for this prompt (queued into a running turn, or an
// extension command handled it). A running turn re-stamps the phase.
if (this.#state.status === "submitted") this.#setStatus("ready");
}
} catch (promptError) {
this.#state.error =
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/harness/pi/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ export const makePiProcessWithDependencies = <R>(
if (before._tag === "Idle") yield* drainQueue(session.chunks);

// Stock Pi before 0.99 omits `data`. 0.99 returns `{ disposition }`.
// pie-pi-process still returns `{ started }`.
// pie-pi-process returns `{ started, disposition }`.
const admission = yield* restore(
session.transport.command<
{ readonly started?: boolean; readonly disposition?: string } | undefined
Expand Down
3 changes: 2 additions & 1 deletion packages/server/src/harness/pi/rpc/rpc-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,8 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<neve
preflightSucceeded = true;
// 0.99 reports disposition instead of a boolean. isStreaming is
// already true for a prompt that just started, so it cannot mean
// "queued".
// "queued". "handled" (an extension command consumed the input)
// starts no turn, so it is forwarded for the host to tell apart.
output(
success(id, "prompt", {
started: disposition === "started",
Expand Down
4 changes: 4 additions & 0 deletions packages/server/test/harness/pi/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ layer(NodeServices.layer)("PiAgent", (it) => {
assert.equal(prompt.started, false);
const chunks = yield* Stream.runCollect(prompt.output);
assert.equal(Array.from(chunks).length, 0);

const next = yield* agent.session.prompt({ sessionId, text: "ping" });
assert.equal(next.started, true);
assert.equal(Array.from(yield* Stream.runCollect(next.output)).at(-1)?.type, "finish");
yield* agent.session.abort(sessionId);
}),
);
Expand Down
Loading