Conversation
The extension loads and works on both harnesses from one build: `pi` keeps its existing behavior, and `oh-my-pi` (omp) gets an equivalent surface where the host differs. Load blocker: `pi.registerEntryRenderer` is pi-only. omp has no entry-renderer concept at all (it renders `custom_message` entries, never the `type: "custom"` records `pi.appendEntry` writes), and the missing method aborted the whole extension load. Registration is now capability-probed; the `bgrun-job` records are still persisted and replayed identically on both hosts. Host differences handled: - Tool presentation: omp unmounts any tool that omits `loadMode: "essential"` and re-exposes it as an `xd://` device. All five tools declare it, spread in rather than written literally (a literal trips pi's excess-property check). - Tool guidance: omp ignores `promptSnippet`/`promptGuidelines`, so the same bullets ride in `description` — emitted once per host, never duplicated. Host identity is probed on two independent signals (CONFIG_DIR_NAME, already load-bearing for config paths, plus the omp-only `registerComposerShape`), because mis-detecting omp as pi would drop the guidance silently. - User config: resolved through the host agent dir (`~/.omp/agent`, profile-aware, via `getAgentDir()`) instead of a hardcoded `~/.pi/agent`, and a failure to resolve it is logged rather than silently falling back to the default profile's directory. - Background work: the stale-job poller uses `ctx.setInterval`/`clearTimer` when the host has them (omp treats an uncaught throw from a raw timer as process-fatal and tears the session down) and a wrapped, unref'd raw interval otherwise, since pi's ExtensionContext has neither. - Diagnostics: routed to `pi.logger` when present (omp's TUI owns the terminal) and to the console on pi, through one module-level sink that also covers the config helpers that run outside the factory. Job panel + status line (both hosts, zero context): the editor panel shows running jobs plus the most recently finished ones while anything is running, self-limited to the 10 lines a `string[]` widget is capped at by BOTH hosts so neither appends its own truncation note; the status line holds `N running` while in flight and the latest outcome once idle. Together they carry what the pi-only transcript card showed, which omp cannot render. Digest diagnostics: a `type` that selects no scorecard now reaches the agent on the wake itself (once per distinct mismatch, inside the existing cap) instead of only the host log, which omp writes to a file the agent never reads. The log line and the wake line are built from shared facts so they cannot drift. Skills: `skill/` renamed to `skills/`, which is the directory omp's plugin provider scans (a manifest `skills` key is ignored there); `pi.skills` repointed so pi keeps loading them, and an `omp` manifest entry added alongside `pi`. Known limit, documented rather than hidden: a job started inside a task/subagent session wakes that child, which has usually already returned. omp's own async job machinery would fix it but is not reachable from an extension (ctx exposes only a read-only snapshot; the registering manager lives on the internal ToolSession; `ctx.invokeTool` is same-name only). The log still lands in the shared jobs dir, so `bgstatus`/`bgtail` recover it from any session. Verified: 200 tests pass, tsc clean, and the extension was driven under omp 18.2.6 in both print mode and a live TUI (panel, status line, toast, wake from the detached exit handler, appendEntry persistence, skill discovery, logger routing).
The recent-finished section in the editor panel was more distracting than useful: a list of old jobs above the editor competes with the work in progress, and `bgstatus` already answers "what ran" on demand. The panel is back to live state alone — header plus one row per running job — and the newest-finished lookup it used remains, feeding the status line only (`⏳ N running`, then `✅ <name> exit=0`), which is what carries an outcome after the panel goes away. Bounding is unchanged in effect and simpler in form: up to eight running rows, then `… N more running`, so a host's own "... (widget truncated)" note still cannot appear. Tests: the finished jobs now have to stay OUT of the panel (and no `exit=` row may appear), the status-line transitions are still pinned, and a 14-job burst proves the panel stays inside the 10-line cap while stating how many rows it hid. Docs updated to match, including the Host differences bullet, which now says plainly that neither surface is a history view. Unreleased (refines d04fc84, which has not shipped), hence no changelog entry.
…pawn Two gaps that made the plugin harder to use than it needs to be on a host that backgrounds long bash calls itself. Native jobs are now visible. oh-my-pi backgrounds long `bash` calls on its own (`bash.autoBackground`, or an explicit `async: true`), tracks them in-process, and delivers their output as an async result. Those are not bgrun jobs — ids look like `bg_1`, there is no log here, and they are cancelled on session switch — but they are background work the session is running, so: - the editor panel lists them after the bgrun rows, tagged `native`, and the status line counts them (`📊 bgrun: 1 running · 1 native`, `⏳ 2 running`); - `bgstatus` lists them under their own heading (running always, finished only with `includeDone`), and `bgstatus bg_5` answers with what that job is and where its output went instead of a bare "no job found"; - `bggrep`/`bgtail` given a `bg_N` id say the same thing, and any unresolvable id now names the expected shape (`<name>-<epoch>-<pid>`). Read-only throughout: bgrun never adopts, cleans, or cancels these. It reads them through `ctx.getAsyncJobSnapshot()`, which upstream pi does not have, so every path degrades to bgrun-only there. The panel is otherwise event-driven and a native job fires none of our events, so it also refreshes on the host's own `tool_execution_end` and on the delivered `async-result` message; both hooks bail out immediately where the snapshot API is absent. Spawning a job that could have been scored now says so. The digest selector keys off `type`, but the vocabulary lives in the project's config, which the agent has no reason to read — the only feedback was a mismatch *after* the job ran, capped per session. When a digest with typed entries is configured and a job is started without a type, the `started:` line names the configured types and the one to pass. The config is already resolved at spawn, so this costs no extra I/O and nothing on the happy path. Guidance, for whichever host the model is on: - the `bgrun` description gains the durability clause (the log survives a restart and stays greppable) — most turns never read the skill, and this is what makes the native-vs-bgrun choice decidable without it; - the skill states why the thresholds differ (>30s is the session, >100 lines is the transcript), which generalizes to hosts not seen yet; - the skill declares which parts are oh-my-pi-only, so a pi-side reader is not hunting for mechanisms that do not exist there; - `bgstatus`'s snippet now advertises host-managed jobs too. Verified on omp 18.2.6: the panel picks up a native job from its tool result and drops it on the async result with no bgrun involvement; `bgstatus` lists and explains native ids; the spawn hint renders as `digest: configured types test, build — pass type: "test" to attach a scorecard`. 208 tests, tsc clean.
The bgrun guideline bullet and the `type` parameter description both ended by telling the model where the digest vocabulary lives (the project's config file), which is an instruction to go read a file the tool now reports itself. Both now name the actual mechanism: pass the matching `type`, and bgrun's `started:` line lists the configured types when you leave it out. The `CONFIG_FILE_HINT` constant existed only to spell that path for those two strings, so it goes with them. Behaviour is unchanged; this is wording the model reads on every request (a promptGuidelines bullet on pi, folded into the description on oh-my-pi), and it is now shorter as well as accurate. Unreleased (refines 78076f2), hence no changelog entry.
Review of the omp port plus four integration gaps found by reading oh-my-pi's source (an adversarial pass and a capability survey of the host). Fixed — all reachable in the shipped code: - A detached `pi.on` aborted the whole extension load on oh-my-pi. `on` is a class method that reads `this.extension` (loader.ts:200), so extracting it to register the host-only events threw `undefined is not an object` and the host dropped the extension entirely. Every method that gets saved to a variable is now called on its owner, and the test harness's `on` throws when detached, so this cannot come back unnoticed. (Caught by loading the extension on a live omp, not by the suite.) - `tool_execution_end` fires on every tool call, and on omp each one re-ran the stale revalidation (a 256 KiB tail read per record with no live child handle) plus two host-side UI re-renders. The rebuild is now gated on the host's job set actually differing — the same signature the status line derives. - `bgstatus`' own not-found path was the one dead end left: it said "No job found" without the id-shape hint that bgtail/bggrep give, so a `bg_N` that had aged out of the snapshot's 5-row window read as if bgrun had never heard of the namespace. It now shares `unknownJobHint`. - `digestTypes` is single-sourced (spawn hint, no-match log line, wake note), so the three surfaces reporting the project's digest vocabulary cannot disagree. Added: - **Native output is readable.** When oh-my-pi truncates a backgrounded bash job's output it spills the full text to a session artifact and advertises the id on the delivered async result. bgrun resolves that id through the session's artifact store, so `bgtail bg_5` / `bggrep bg_5` read the host's own spill through the same bounded readers used for bgrun logs, stamped with its provenance (no exit marker, not ours to clean), and `bgstatus bg_5` names the path. In-memory on purpose: `bg_N` ids restart at 1 and artifact ids are session-scoped, so a persisted mapping could point at another job's output. Nothing is written, adopted, or cleaned. Verified live on omp 18.2.6 with a 165 KB `seq 1 30000`: artifact `0` read back by both readers. - **Native ids now name their remedy** — `hub cancel ids:["bg_5"]`, `hub jobs`, `/jobs` — in every message that mentions a native job. Those jobs are the host's to steer; bgrun only says how. - **A settled-but-undelivered result is visible.** The snapshot's delivery queue is the one native state nothing else reports, so the status line adds `N result pending` in the window between a job settling and its result being injected. - **The index follows the transcript.** oh-my-pi does not re-emit session_start when the transcript changes: `/new`, `/resume`, a fork and a tree branch emit `session_switch`/`session_branch` (its own internal bridge re-initialises on all three), so a fresh session kept reporting the previous one's jobs. Finished jobs are dropped with the transcript that ran them; running jobs stay. 211 tests (3 new: the artifact bridge with provenance, the pending-delivery count, and the transcript-change re-index), tsc clean.
`condenseLogLines` merged every consecutive duplicate but annotated only runs of
3+, so an exact pair was emitted as one bare line: the reader saw fewer lines
than the log had, with nothing on screen to say a line was gone. A pair now
emits verbatim (twice); runs of 3+ still fold into one line carrying `[xN]`,
which is what makes a fold legible.
Reported as "not condensing the correct number of lines", and confirmed against
the code's own rule: 8 lines in, 5 out — `["A","dup","dup","B","trip"x3,"C"]`
became A / dup / B / trip [x3] / C.
- Total-cap budgeting moved inside the per-emission loop. It counted one line
per run, so an unfolded run could overrun the cap.
- Pinned the boundary end-to-end (spawn → wake → bgtail): a pair reaches the
reader twice with no note, a triple folds to `spinner [x3]` with exactly one
run note. The pair case was untested before, which is why this survived.
- The bgtail/bggrep notes and descriptions no longer name `ctx_execute_file`.
It is context-mode's sandboxed reader, not a pi or oh-my-pi tool — neither
host ships it, and neither host ships an equivalent — so telling an agent to
run it could point at nothing. They now say to read the log path directly;
the skills keep the recipe but mark it environment-provided, and the
surrounding prose says how to use it when it exists.
Public docs state the rule the same way ("repeated lines collapsed") and already
described the size/condensation behavior, so only the pair clause needed adding.
212 tests, tsc clean.
The `/bgtail` slash command calls the same core as the `bgtail` tool but skipped `annotateNative`, so a human reading a native job's spilled artifact got the host's file with no indication of its provenance — the one thing the stamp exists to say (no exit marker, not ours to clean). The tool path and the command path now behave identically, and the test asserts it through the command handler. The core's note records the convention: it returns the read unstamped, because the stamp needs the job id the caller already has. Found by reviewing the branch before the PR, not by the suite.
…eview Four reviews (runtime, parity/tests, security, docs) over the branch. The two that mattered were data defects the suite could not see, plus one reachable injection path. Native output could be served for the wrong job: - `nativeOutputs` was never invalidated. oh-my-pi mints `bg_N` from the first free slot after evicting a settled row (~30s after its result is consumed), while a spilled artifact stays on disk for the session — so the same id comes back as a *different* job, and `bgtail bg_1`/`bgstatus bg_1` would read and stamp the previous job's output. Entries now carry the job's `startTime` (looked up settled-first at delivery time, deliberately: `running` may already hold the newcomer) and are dropped when the id's row no longer matches; the map is also cleared on a transcript change, where the host recycles the whole id space. - The delta-tail bookmark was keyed by job id with no path, so a remapped artifact whose first line matched and whose size had not shrunk read as "the same log, one new line" — silently hiding the head of a file the reader had never seen. The bookmark now carries the resolved path and a change resets to a full tail under its own header. Host strings reached terminal-interpreted surfaces unstripped: - oh-my-pi takes a background job's `label` verbatim from the command line, and the panel, status line and `bgstatus` rendered it (and ids) raw — so a label carrying ESC could set a window title, plant a hyperlink, clear the screen, or forge an extra panel row. The strip the extension already applied to its own job names is now the single rule for host text too: applied at the snapshot read (label/type) and at every site that displays an id, including the bgrun command fallback in the new status line. - A native artifact path was believed on `existsSync` alone. It is now required to sit inside the session's artifact directory (realpath-checked) and the id to be well-formed; the write side validates, caps the delivered batch and bounds the map. The branch's own test had pinned the trusting behaviour — it now models a real artifacts directory and asserts both directions. - `annotateNative` stamped failed reads as "the host's spill", asserting provenance for output nobody read. Error results pass through unstamped. Parity: - `unknownJobHint` inferred "native" from the id shape alone, so upstream pi — no job manager, no artifacts, no `hub` tool, no `/jobs` — was told its `bg_N` id was a host-managed job and to cancel it with a tool that does not exist there. The hint now takes the same probe the callers already hold. - Four tests could not fail: the `session_branch` re-index (asserted state that was already true), the native-refresh gate (never exercised in either direction), the managed-timer integration (the fake never provided managed timers), and the "other messages are ignored" filter (the gate masked it). Each now changes the state only the behaviour under test can account for. - The fake session manager now enforces the receiver rule its real methods have, the way the fake `on` already did. Docs (from the documentation review): - The `bggrep` `bytes` guidance named 67108864 as "the whole capped log", which is short of it by the wrapper's 4 KiB of notices and exit marker — the head it was asked for stays unread. README rows aligned to the same "ceiling + 4 KiB". - README's "two presentation details" now lists what it actually lists, the CI workflow's hand-run test count is current, and two source comments no longer spell pi's config dir where the code uses `$CONFIG_DIR`. Verified: 216 tests (4 new: recycled id, remapped artifact, hostile label, managed-timer integration), tsc clean, and re-run live on oh-my-pi 18.2.6 — a 165 KB spilled artifact still resolves and reads under the containment rule, and the bgrun path is unchanged end to end.
CI report
Ref: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: run on oh-my-pi, reporting its background jobs too
One extension, two hosts:
pi-background-runnow runs under oh-my-pi as well asupstream pi, and treats oh-my-pi's own backgrounded bash jobs as first-class
background work instead of a second namespace the agent has to learn.
Why
On oh-my-pi the harness backgrounds long
bashcalls itself(
bash.autoBackground, orasync: true) with ids likebg_1— and those wereinvisible to the extension. Live sessions showed the cost: an agent reads a
bg_5id, callsbggrep bg_5, and gets "no log found" with no way to find outwhat
bg_5is or where its output went. Two background systems, one of themunexplained.
What changed
Both hosts, one codebase. The extension resolves the host's config dir
(
.pi/.omp), agent dir, logger, managed timers and tool-guidance fieldsthrough capability probes — never a host name check. Everything oh-my-pi adds is
absent on pi, where each path degrades to bgrun-only.
ctx.ui.setWidget) + status line (ctx.ui.setStatus), the twosurfaces that carry job state on both hosts at no context cost. The panel is
live-only (running jobs), bounded to the 10 lines both hosts cap a widget at,
with
… N more runninginstead of a silently dropped tail.renderer, so there the panel and status line carry the same information.
loadMode: "essential"— otherwise thehost demotes them to
xd://devices that have to be discovered before use.Native jobs are visible and readable.
bgstatuslists them under their own heading,bgstatus bg_5answers withits state (never a bare "not found"), the panel tags them
native, and thestatus line counts them.
session artifact and advertises the id on the delivered async result. The
extension resolves that id through the session artifact store, so
bgtail bg_5/bggrep bg_5read the host's own spill through the samebounded readers used for bgrun logs — stamped with its provenance (no exit
marker, not ours to clean), and
bgstatus bg_5names the path.hub cancel ids:["bg_5"],hub jobs,/jobs). Those jobs are the host's to steer; the extension only reports them.N result pendingin the window between a native jobsettling and its result being injected — the one state nothing else reports.
The index follows the transcript. oh-my-pi does not re-emit
session_startwhen the transcript changes:
/new,/resume, a fork and a tree branch emitsession_switch/session_branchinstead (its own internal event bridgere-initialises on all three). A fresh session used to keep reporting the previous
one's jobs. Finished jobs are dropped with the transcript that ran them; running
jobs stay, since they outlive it.
Guidance. Spawning a job that could have been scored now says so: when a
digest is configured with
typeentries and the job has none, thestarted:line names the vocabulary and the type to pass — the one moment the answer is
still actionable, and no extra I/O since the config is already resolved. The
bgrundescription gained the durability clause that makes the native-vs-bgrunchoice decidable on turns that never read the skill, and the skill states the
rationale behind its thresholds and which parts are oh-my-pi-only.
Fixes found along the way.
pi.onaborted the entire extension load on oh-my-pi:onis aclass method that reads
this.extension, so saving it to a variable threw andthe host dropped the extension. The test harness now enforces the same
receiver rule, so this cannot return unnoticed.
tool_execution_endfires on every tool call; on oh-my-pi each one re-ran thestale revalidation (a 256 KiB tail read per handle-less record) and two
UI re-renders. The rebuild is now gated on the host's job set actually
differing.
bgstatus' own not-found path was the last dead end left: it omitted theid-shape hint that
bgtail/bggrepgive, which triggered exactly when abg_Nhad aged out of the snapshot's 5-row window.condenseLogLinesfolded an exact repeated pair into one bare line, so thereader saw fewer lines than the log had with nothing to say a line was gone.
A pair is now emitted verbatim; runs of 3+ still fold into one line carrying
its
[xN]count, and total-cap budgeting counts what is actually emitted.ctx_execute_filereferences left the agent-facing strings: it iscontext-mode's sandboxed reader, not a pi or oh-my-pi tool (neither host ships
it), so telling an agent to run it could point at nothing. Those strings now
say to read the log path directly; the skills keep the recipe but mark it
environment-provided.
Adversarial review
Four independent reviews (runtime paths, host parity + test quality, security,
docs-vs-code) ran against the branch before this PR. Everything they found is
fixed; the three that a reviewer should care about:
bg_Nid served the previous job's output. oh-my-pi mintsbg_Nfrom the first free slot after evicting a settled row (~30s after itsresult is consumed) while the spilled artifact stays on disk for the session,
so
bgtail bg_1could read — and stamp as this job's — the previous job'slog. Entries now carry the job's
startTime(looked up settled-first atdelivery, since
runningmay already hold the newcomer), are dropped when theid's row no longer matches, and the map is cleared on a transcript change,
where the host recycles the whole id space.
with no path, so a new file with a matching first line and a non-shrinking size
looked like "the same log, one new line", hiding its head. The bookmark now
carries the resolved path and a change resets to a full tail.
labelverbatim from the command line, so a label carrying ESC could set awindow title, plant a hyperlink, or forge an extra panel row. The strip the
extension already applied to its own names now covers host text at the
snapshot read and at every display site. The artifact path is also required to
sit inside the session's artifact directory rather than being believed on
existsSyncalone, and failed reads are no longer stamped as the host's spill.Also fixed from that pass:
unknownJobHintno longer describes a job manager(and a
hubremedy) to a host that has neither; four tests that could not failwere rewritten so they can (the
session_branchre-index, the refresh gate, themanaged-timer integration, and the async-result filter); the fake session manager
now enforces the receiver rule its real methods have; and the
bggrepbytesguidance no longer names a value that stops short of a capped log.
Verified
surface;
tsc --noEmitclean on both.and containment), id recycling, the artifact remap, a hostile label, the
pending-delivery count, the transcript-change re-index, the managed-timer
integration, the panel's native rows, the spawn-time digest hint, and the
condenser's fold boundary. The guards bite: reverting the recycling check or
the control-char strip fails the matching test.
seq 1 30000backgrounded withasync: true—bgstatus bg_1reported the artifact path,bggrep bg_1returnedline-numbered matches out of 30,000 lines,
bgtail bg_1read the tail, allstamped as the host's spill (re-checked after the containment rule landed).
The panel picks up a native job from its tool result and drops it on the
delivered async result with no bgrun involvement; the condenser fix was
re-checked through the real tool path (
dup / dup / spinner [x3] / done).npm pack --dry-runships 7 files (extension, both skills, README, LICENSE,package.json) and no test files;
bun install --frozen-lockfileis a no-op.Deliberate non-goals
internal
@oh-my-pi/pi-coding-agent/asyncsubpath that would allow it:unsupported, host-version-fragile, and absent on pi. The extension reports
them and names the host's own remedy.
AsyncJobManager/delivery control,session_stopdeferral, transcriptcards via
registerMessageRenderer, widget component factories,saveArtifactdigests, compaction and memory hooks — each considered and skipped, with the
reasoning in the relevant commit bodies. Transcript cards in particular would
put
custom_messageentries into LLM context, which is what this plugin existsto avoid.
tool_callrewrite/block): silentlyredirecting a user's command is magic, and blocking would fight the harness's
own auto-background.
bg_Nids restart at 1 andartifact ids are session-scoped, so a persisted mapping could resolve to a
different job's output after a restart.
For the reviewer
main(7d10856), 9 commits, nothing pushed to thisbranch yet. The repo squash-merges, so the title above is what release-please
parses:
feat→ minor bump pre-1.0 (0.6.0 → 0.7.0); thedocs,refactorand
testcommits are hidden from the changelog.bun install --frozen-lockfile,tsc --noEmit,bun test, and thenpm packallowlist check — all four pass locally.