Files
command-center/agent/managed-skills/omp-session-journal-cursor-access/SKILL.md
T

3.8 KiB
Raw Blame History

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 → 11 while idle=false, leaf ids rotating.
  • Append lags message_end by ~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 on session_shutdown. Use these, never raw setInterval. 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_compact and session_branch are 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.