Skip to content

feat(ai): Ask this session (core + Pi) - #1668

Merged
backnotprop merged 4 commits into
mainfrom
feat/ask-this-session
Oct 3, 2026
Merged

backnotprop merged 4 commits into
mainfrom
feat/ask-this-session

Conversation

@backnotprop

Copy link
Copy Markdown
Owner

Phase 1 of "Ask this session" (build order steps 1-3 of .product/drafts/session-ask-ai/DESIGN.md): Ask AI answered by the agent session that opened Plannotator.

Core (packages/ai/session-bridge.ts, vendored to Pi)

  • SessionBridge host interface (ready | busy | blocked | gone, turn/transient modes, ask, optional interrupt) and a session-bridge provider behind the existing /api/ai/* endpoints in both runtimes. It is registered last, so the server default does not change.
  • One question at a time across threads. No system prompt; the question starts with a Plannotator header line.
  • A busy session answers agent_busy. The client then re-asks with busyPolicy: "wait" or "interrupt" on /api/ai/query.
  • Abort cancels only our question. The runtime detaches the bridge before teardown, so a decision or exit never stops a turn that is already running.

UI

  • "Ask this session · Pi" with no model picker. It is the default when the bridge can answer; a saved explicit choice still wins.
  • Busy shows "Ask when it finishes" / "Interrupt and ask now". Gone or blocked shows "Ask a separate AI instead", which switches this page only (the cookie is not written) and re-asks.
  • Code review sends the diff's identity, not the patch.
  • Hosts without a bridge get the same requests and DOM as before: no new fields and no new elements.

Pi

  • pi-session-bridge.ts covers review, annotate and last. Each question is a real turn (sendMessage with customType: "plannotator-ask" and triggerTurn), streamed from the session's events.
  • Off in remote mode.
  • Pi plan review is unchanged (see below).
  • Fix: the Pi server never cancelled the SSE stream when the client disconnected, so closing the tab or superseding a question left the turn running. The Bun server already handled this. Covered by a Node-run test.

Where Pi differed from the design

  • pi.on returns void in the installed Pi 0.84/0.85; only 1.0 returns an unsubscribe. So the listeners are registered once at load and route events to the active question.
  • pi.sendMessage returns void and reports async failures to Pi's own error channel. A watchdog fails a question that never reaches the session.
  • "Interrupt and ask now" uses ctx.abort() and then asks. Pi's steer would also work, but it merges the question into the user's turn.

Verified live in Pi 0.85.1 (RPC mode, real model) for annotate, last and review:

  • streamed answer, and the question is in the transcript;
  • agent_busy, then wait, then the answer;
  • interrupt, then the answer;
  • Stop ends Pi's turn in about 100 ms;
  • new_session gives session_gone.

A headless Chromium run of the annotate UI showed the label, no model picker, the busy choice, the waiting status and the answer.

Tests: typecheck, bun test, the CI DOM lists, build:pi and the guides.show manifest check all pass.

Not in this PR: Pi plan review. submit_plan awaits openPlanReviewBrowser inside a sequential tool, so the session is blocked until the decision. To make it non-blocking:

  1. Return from the tool right away with a "plan under review" result.
  2. Deliver the decision later as a follow-up message, as review/annotate already do.
  3. Move the approve-side phase transitions (tools, model, thinking level, todo sync) from the tool result into that follow-up path.
  4. Guard against the agent continuing to work while the plan is pending, for example by keeping planning-phase tool scoping until the decision arrives.
  5. Pass the bridge to startPlanReviewServer (the Bun option already exists).

Ask AI can be answered by the agent session that opened Plannotator.
Adds the host-neutral SessionBridge interface and the session-bridge
provider behind the existing /api/ai/* endpoints (both runtimes), the
UI default/busy/fallback flow, and the Pi bridge for review, annotate
and last. Also cancels the SSE stream on client disconnect in the Pi
server, matching the Bun server.
…r-started question

agent_end fires once per agent run, and Pi retries a retryable error
(overloaded, rate limited) or continues after an overflow compaction
inside the same prompt. The bridge reported the first failed attempt as
the answer and stopped listening while Pi went on to answer. An error
end now waits for agent_settled (Pi >= 0.80.4), or for the session to
go idle on older Pi, and a retry that starts answering clears it.

The start watchdog checked once at 15s and gave up silently when the
session was busy, so a question steered into a turn that Pi then
dropped (user aborted it) waited forever. It now re-checks until the
session is idle.
… guard)

A bridge question is a real turn, with tools, in the user's own agent
session. Nothing checked the Host header on /api/ai/*, so a DNS-rebinding
page (evil.example resolved to 127.0.0.1) could create a session-bridge
session or query one. Creating a session on, or querying, the
session-bridge provider now requires the runtime's
authorizeSessionBridgeRequest guard to pass: both runtimes check that the
Host header is localhost, a 127.x literal or [::1] with the server's own
bound port, and refuse with 403 { code: session_bridge_forbidden_host }.
Without a guard the bridge never answers. Other providers never consult
it, so non-bridge Ask AI is unchanged.

The predicate lives in packages/shared/loopback-host.ts (vendored to Pi),
which now owns isLoopbackHostname; live-proxy-core re-exports it. The VS
Code cookie proxy forwards Host as the session URL's host (localhost:PORT),
so it passes.

session-bridge.ts drops TypeScript parameter properties: endpoints.ts now
imports it, and Pi's AI runtime must still load under Node's strip-only
TypeScript (ai-runtime-disconnect.test.ts runs it in a real Node).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant