3.8 KiB
name, description
| name | description |
|---|---|
| omp-session-journal-cursor-access | Read a live omp session's transcript mid-run from an extension (cursor = journal entry id via ctx.sessionManager), with the entry-type noise list, append lag, and the ctx.setInterval metronome. |
Reading the live session journal from an extension
Use when an extension must summarise/narrate/index everything said since some point
rather than reacting to one event payload, or when you need a durable "where I left off"
pointer into a session. Verified 2026-10-30 with a throwaway probe under omp -p
(v18.12.5), 1 s ticks over a two-tool-round run.
The accessor (no plugin install needed)
ctx.sessionManager is on every ExtensionContext (ReadonlySessionManager,
@oh-my-pi/pi-coding-agent/dist/types/extensibility/extensions/types.d.ts:485):
getEntries() getBranch() getEntry(id) getLeafId() getLeafEntry()
SessionMessageEntry = { id, type: "message", message: AgentMessage }
- The cursor is an entry id. Persist
getLeafId()(or the id of the last entry you actually consumed); "everything since" = entries after that id. Advance is O(1). getEntries()grows mid-run — proven by a timer, with no handler help:entries=3 → 6 → 7 → 9 → 11whileidle=false, leaf ids rotating.- Append lags
message_endby ~0.5–1 s. So "the live edge" always under-reads by a second. Consume only up to the last id you actually read; the sub-second tail rides into the next window. Never advance past what you read. ctx.setInterval(cb, ms)/ctx.setTimeout(types.d.ts:555-572): throws contained,unref'd, auto-cleared onsession_shutdown. Use these, never rawsetInterval.ctx.isIdle()is the cheap "is this session even working" guard.
The journal is not the conversation (allowlist, never denylist)
agent_end reported msgs=6 while the journal held 11 entries. Bookkeeping types in the
union: custom (zero content, ~3 per tool round), model_usage, thinking_level_change,
mode_change, model_change, service_tier_change, label, title_change,
ttsr_injection, session_init, credential_pin, reset_boundary. Speech-relevant:
message (roles user / assistant / toolResult) and, as context, compaction /
branch_summary. New types appear on the harness's own schedule → an allowlist keeps them
silent until someone decides they are speech.
Re-export the per-message readers instead of re-parsing content: an assistant message's
narration-flagged thinking part is answer text, unflagged thinking is deliberation
(narration: true discriminator).
Robustness rules that came out of the failure modes
- Empty window ⇒ leave the cursor where it was (nothing new must not read as news).
- Cursor id no longer in the journal (compaction, branch rewrite) ⇒ treat as "from the top
of what is left", never as "nothing" — a lost pointer must not mute a session forever.
session_compactandsession_branchare dispatched events if you want to re-anchor eagerly.
Probe recipe (proves any of the above in ~60 s, touches nothing live)
Project-local extensions load without installing: write <run-dir>/.omp/extensions/probe.js
(default-export function (pi)), log to an absolute path, then
: > /tmp/probe.log
timeout 220 omp -p --thinking off --cwd <run-dir> \
"Run these bash commands strictly one after the other: first: sleep 7; echo FIRST. then: sleep 7; echo SECOND. Then reply: FIRST SECOND"
Register the tick inside session_start (ctx.setInterval(() => stamp(info(ctx)), 1000)),
and give every entry you expect to exclude a speech-shaped payload with a sentinel word —
an absence assertion with an empty producer proves nothing. Do NOT pass --no-session:
the journal is the thing under test. Teardown: nothing to undo (no plugin link, no
~/.omp/plugins/package.json write) — delete the run dir.