live: heartbeat sync 2026-10-30T21:32:40-07:00 (Refs #11)

This commit is contained in:
Frank committed 2026-10-30 21:32:40 -07:00
1 parent d03fe57bbd
commit d627368f71
13 files changed
+280 -26

No files matched your search

@@ -0,0 +1,70 @@
---
name: omp-session-journal-cursor-access
description: "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
```bash
: > /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.