Skip to content

feat(pi): drive the visible pane, read replies from pi's own session log - #283

Open
agnitum2009 wants to merge 1 commit into
SeemSeam:mainfrom
agnitum2009:feat/pi-pane-driven-execution
Open

feat(pi): drive the visible pane, read replies from pi's own session log#283
agnitum2009 wants to merge 1 commit into
SeemSeam:mainfrom
agnitum2009:feat/pi-pane-driven-execution

Conversation

@agnitum2009

Copy link
Copy Markdown
Contributor

What

pi seats run headless: every job spawns its own pi run subprocess, so the agent pane sits at its startup banner and the seat's work is invisible — unlike claude and kimi seats, which drive their pane.

This sends the prompt to the pane instead, and takes the reply from the JSONL session log pi writes under its --session-dir.

Why read the session log instead of scraping the pane

I tried pane_quiet_support first. It does not hold up for pi, because pi renders its reasoning into the pane and the parser folds that reasoning into the reply whenever the thinking text does not happen to contain the done marker:

reply: 'The user asks which directory. I should answer directly in one sentence.\n
        Let me check the cwd shown in the footer.\n\n我现在运行在目录 /home/umax/work/n14。'

Filtering that would mean another prefix blacklist like _looks_like_kimi_non_answer. pi's own session log makes it unnecessary — thinking and text are separately typed content blocks, messages chain by parentId, and stopReason is explicit:

{"type":"message","id":"f0996678","parentId":"40bf081e","message":{
  "role":"assistant",
  "content":[{"type":"thinking","thinking":"..."},{"type":"text","text":"..."}],
  "stopReason":"stop","model":"k3"}}

So the reply and the turn boundary are both exact, with no heuristics. This mirrors what KimiProviderAdapter does with native_turn_log, where pane text is only rescue evidence.

Behaviour

  • stopReason: tool_use keeps the turn open, so tool rounds are not cut short
  • the parentId walk stops at the next anchored user turn
  • a partially flushed trailing JSONL record is tolerated (normal while pi is writing)
  • CCB_PI_EXECUTION_MODE=headless restores the previous adapter
  • CCB_PI_NATIVE_TURN_TIMEOUT_S overrides the 300s turn budget

One semantic change worth calling out for review: pane mode shares a single pi session, so a pi seat now accumulates context across jobs instead of starting fresh per job. That matches claude/kimi seat behaviour, but it is a change for anyone relying on pi jobs being isolated.

Layout

execution.py keeps the headless adapter and becomes a thin mode selector. The pane path is split so no file exceeds 400 lines:

file lines role
pane_native_log.py 222 session-log observer
pane_execution.py 388 adapter + submission lifecycle
pane_support.py 187 terminal decision + runtime helpers

Verification

Ran against a live CCB project (coder2:pi, model k3) on this branch's code:

  • plain turn — reason=pi_pane_turn_end, reply exact, 10.5s
  • tool turn — read + wc -l rounds, 30s; reply is the final assistant message, not the intermediate tool_use one. Its LINES=54 claim independently confirmed with wc -l
  • pane visibility — prompt, reasoning, tool calls and answer all render live in the pane
  • reasoning isolation — both replies clean (28 / 43 chars) with reasoning visible in the pane and absent from the reply

test/test_pi_pane_native_log.py adds 9 unit tests covering thinking exclusion, tool_use not completing, the tool-round chain walk, the next-turn boundary, anchor-before-reply, unknown request, sibling session files, partial trailing record, and a missing session dir.

Note: the end-to-end runs above were made before the file split; the split is a pure code move verified by import checks and the unit tests, not by a second live round trip.

pi seats ran headless: every job spawned its own `pi run` subprocess, so the
agent pane sat at its startup banner and the seat's work was invisible, unlike
claude and kimi seats.

Send the prompt to the pane instead, and take the reply from the JSONL session
log pi writes under its --session-dir. That log types `thinking` and `text` as
separate content blocks, chains messages by parentId, and carries an explicit
stopReason, so the reply and the turn boundary are both exact.

Scraping the pane was tried first and rejected: pi renders its reasoning into
the pane, and pane_quiet_support's parser folded that reasoning into the reply
whenever the thinking text did not happen to contain the done marker.

- stopReason tool_use keeps the turn open, so tool rounds are not cut short
- the parentId walk stops at the next anchored user turn
- CCB_PI_EXECUTION_MODE=headless restores the previous adapter
- CCB_PI_NATIVE_TURN_TIMEOUT_S overrides the 300s turn budget

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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