Skip to content

presence: show the model name only (drop the provider prefix) - #59

Merged
frolic merged 1 commit into
frolic:mainfrom
hello-pollux:pollux/presence-model-name
Oct 17, 2026
Merged

frolic merged 1 commit into
frolic:mainfrom
hello-pollux:pollux/presence-model-name

Conversation

@hello-pollux

Copy link
Copy Markdown
Contributor

Frolic's ask: the bot's Discord activity showed deepseek/deepseek-v4.1-flash-1017; trim it to everything after the last slash → deepseek-v4.1-flash-1017.

const text = model.id.split("/").pop() || model.id;
  • split("/").pop() returns the whole string unchanged when the model id has no provider segment, so a bare id still renders correctly.
  • Renamed the dedupe variable currentPresenceModel → currentPresenceText (it holds presence text, not a provider/model pair) and updated the doc comment.
  • tsc --noEmit clean.

Not covered by a test — there's no existing test for presence, and this is a one-line string transform. Say the word if you'd like one.

Discord activity read "deepseek/deepseek-v4.1-flash-1017"; now it reads
"deepseek-v4.1-flash-1017". split("/").pop() handles a bare model id
(no provider segment) by returning it unchanged.
@frolic
frolic merged commit 6efc12a into frolic:main Oct 17, 2026
1 check passed
frolic pushed a commit that referenced this pull request Oct 17, 2026
Fixes a steering bug that the pi 1.3.0 bump widened. Base merged, so
this is now a clean single-concern PR on `main` — the diff is the fix
commit plus its test, nothing else.

Main has moved since this branch was cut (#59, "presence: show the model
name only"), which touches `createAgentPool.ts` in the same file. Merged
rather than rebased, per the repo's convention; the merge was clean and
#59's change is intact.

## The bug

pi has two busy flags and they flip at different points:

- `session.isStreaming` — pi's `_isAgentRunActive`
- `session.agent.state.isStreaming` — the agent loop's flag, which
guards `activeRun`

`createAgentPool.handle()` gated steering on the **agent** flag. But pi
1.2.0 moved the **session** flag to the first line of its run-start
sequence, which then awaits the compaction check and the
`before_agent_start` handlers; the agent flag only becomes true inside
the later `agent.prompt()` call.

So during that window a message arriving from Discord found the agent
flag `false`, the harness concluded nothing was in flight, and it
submitted a plain `prompt(text)` with no `streamingBehavior`. pi checks
*its* flag, sees busy, and throws:

```
Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.
```

`handle()` catches that and posts it via `sender.sendError` — so the
user gets an error line and **their message is never processed**. On
0.84.4 the two flags flipped one statement apart, so this was
effectively unreachable; on 1.3.0 the window spans the whole
run-preparation, which is exactly when someone fires off a quick
correction.

`evict()` read the same flag for its busy check. Benign there, but wrong
for the same reason — a just-started session could be evicted.

## Reproducing it

The window is normally sub-second, so I widened it deterministically by
registering a ~2s `before_agent_start` handler in a throwaway agent
home, then probing both flags while a run was starting:

```
PASS  observed the gap: session busy while agent.state.isStreaming is false
PASS  session.isStreaming === true during the gap
PASS  agent.state.isStreaming === false during the gap
PASS  a plain prompt during the gap is REJECTED (today's harness path)
PASS  the rejection is the 'already processing' error
PASS  a steer during the gap is ACCEPTED (the fixed path)
PASS  the steered message landed in the session
```

Both flags read and both prompt paths exercised in the *same* window, so
this is the bug and the fix landing on the same measurement, not two
separate runs.

## The fix

Read `session.isStreaming` at all three sites, via a small
`isSessionBusy()` helper that carries the explanation — this is a trap
someone will otherwise walk back into. `handle()` also now reads the
flag once and uses that value for both the option it builds and the
branch it takes; those must agree or the prompt carries the wrong
disposition.

**Why this can't regress anything:** the session flag is cleared in
`_emitAgentSettled()`, which runs after `agent.prompt()` and the
post-run loop finish — so it is strictly *wider* than the agent flag,
never narrower. Every case that previously steered still steers, and the
only newly-steered case is the window that previously errored.

## What I deliberately didn't do

- **Catch-and-resubmit-as-steer.** Tempting as a belt-and-braces, but pi
has no typed error class for this — all five "already processing" throws
are bare `new Error()` with different messages, so it would be a string
match against prose. That is brittle and would mask genuine "already
processing" bugs. Reading the authoritative flag makes the error
unreachable on this path instead.
- **`wakeUp()` keeps a plain `prompt()`.** Its three callers
(`thread.ts` on a brand-new thread, `recoverActive` on a fresh process,
`handleClear` after `dropEntry`) all target a session that is fresh or
just dropped, so it can't be busy. If a caller is ever added that can
hit a warm busy session, it needs the same gate — noted in the code.

## Tests

Added `src/agent/buildPromptOptions.test.ts` (4 cases) pinning the
`isBusy` → `streamingBehavior: "steer"` pairing, which is the contract
this bug violated. `buildPromptOptions` was the one piece of this change
with a testable seam — the pool itself needs a live discord.js client
and a full pi session to construct.

`bun run typecheck` clean, `bun test` 106/106.
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.

2 participants