Repository navigation
Add Codex plugin sharing a host-agnostic stop-hook core - #6
Merged
Merged
Conversation
Codex now installs the Mainframe plugin with the same share-video skill,
hosted MCP wiring, and AFK stop hook as Cursor. The Codex Stop hook returns
`{ decision: "block", reason }` to nudge an away session toward a video
summary, guarded by `stop_hook_active` to avoid continuation loops.
Refactor the stop-hook runtime so hosts share one core:
- `hooks/core/transcript.ts` holds host-agnostic primitives (timestamp and
Mainframe-URL parsing, file reading, summary shaping).
- Cursor transcript parsing moves to `hooks/cursor/transcript.ts`; Codex
rollout parsing lives in `hooks/codex/transcript.ts`, ignoring unrelated
event types while failing closed on corruption or a missing `session_meta`.
- `stop-policy` exposes `decideStop(summary, stopTimeMs)` shared by both hosts.
`tooling/generate.ts` emits `.codex-plugin/plugin.json` and the
`.agents/plugins/marketplace.json` catalog (a Git-URL source rooted at the
repo so both plugins share `skills/`, `./.mcp.json`, and `dist/`). The package
surface, drift check, and release archive now cover both hosts.
Describe installing the Codex plugin via `codex plugin marketplace add` and note the shared share-video skill, MCP wiring, and stop hook. Update the agent notes to cover both supported hosts and the generated metadata locations.
Code-quality pass from the thermo-nuclear review. The file/text read,
size-guard, and summary-shaping skeleton was copy-pasted in both
hooks/cursor/transcript.ts and hooks/codex/transcript.ts, differing only in the
row parser — duplication that a third host would copy again.
Lift that skeleton into core `summarizeTranscript(text, parseRows)` and
`summarizeTranscriptFile(path, parseRows)` parameterized by a named
`TranscriptRowParser`. Each host now contributes only its parser behind a thin
named binding, making "a host is a row parser" the explicit extension point.
`readTranscriptText`, `MAX_TRANSCRIPT_BYTES`, and the summary shaping become
private to core. Also simplify Codex `classifyCodexRow` to a plain string union
instead of payload-free `{ kind }` objects. Behavior is unchanged.
Acts on the second code-quality review (no blockers; DRY sharpening + test gaps). - Extract the JSONL "parse or fail closed" scaffold (split, trim, `JSON.parse`, object-check) into core `parseJsonlRecords`, so the untrusted-transcript -> trusted-rows guarantee lives in one chokepoint for every host. This also narrows Cursor's previously over-broad try/catch to the parse step only. - Add core `runStopHookCli(evaluate)` and make both host stop-evaluators pure (no stdin/stdout I/O), separating orchestration from policy. - Close two Codex test gaps: work state resets per user turn, and a stop-level "no work since last user message" negative case. Behavior unchanged; 68 tests pass and both compiled hooks were smoke-tested.
Follow-up to a code-quality review (APPROVE; no blockers). Add two Codex parser tests that lock guarantees the review flagged as untested: - a synthetic `response_item` user row injected after real work does not steal the turn (last user time and workHappened stay anchored to the real `event_msg/user_message`) - a latest-user timestamp outside the supported epoch range fails closed to `missing-user-time` (so the hook never fires on a garbage timestamp) Tests only; no behavior change. 70 tests pass.
Addresses two Bugbot findings on the Codex transcript parser: - Work detection only recognized `function_call`, so sessions whose work ran through other Responses-API tool calls (`local_shell_call`, `custom_tool_call`, `web_search_call`, ...) reported no work and never triggered the share suggestion. Match the whole `*_call` family instead. - User rows overwrote `lastUserTimeMs` without an ordering check, so a backwards timestamp could inflate the away duration and fire the hook spuriously. Fail closed on non-decreasing user timestamps, matching the Cursor parser and AGENTS.md's fail-closed rule. Adds tests for both. 72 tests pass.
Self-review of the prior Bugbot fixes: the monotonic user-timestamp guard added to the Codex parser was a verbatim copy of the Cursor parser's block, so the same correctness-critical rule lived in two places. Extract it to core `nextUserTimeMs(rawTimestamp, previousUserTimeMs)` and call it from both parsers, leaving only the trivial per-host state assignments inline. One definition of "fail closed when a user timestamp moves backwards" for every host. Behavior unchanged; 72 tests pass.
Contributor
Author
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 34f631f. Configure here.
Update the shared plugin description copy and regenerate the Cursor/Codex manifests and marketplace metadata. Tests assert the new literal.
Drop implementation details (stop hook, MCP wiring, hook trust) from the Cursor and Codex install sections; describe what the plugin gives users.
Short description: drop the "Mainframe" brand name (it doesn't tell a new reader what the plugin does) -> "Create and share short video updates from agent work." Long description: replace the feature/implementation blurb with the value: agents now outproduce what anyone can review, so the agent sends you a short video of what it did, in your own voice and branding, shareable with your team. Regenerated manifests/marketplace; tests assert the new literals.
Lead with what you get out of it ("have your agents record short videos so you
can stay up to date") rather than dwelling on the problem.
"Have your agents record..." read like an awkward imperative; use the plain declarative "Your agents record short videos of what they did, so you can..."
Anchor the opener to the delivery moment ("When your agent finishes a task, it
sends you a short video of what it did...") and close on the benefit, replacing
the flatter "Your agents record..." opener.
Lead with the payoff ("watch what your agents did instead of reading through
all of it") and keep the voice/branding/glance/share specifics.
vincentmvdm
marked this pull request as ready for review
June 3, 2026 22:03
jordansinger
approved these changes
Jun 3, 2026
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.
Tip
🎬 Watch the walkthrough: Add Codex plugin with host-agnostic stop-hook core
Generated by Mainframe. Mainframe automatically generates videos when new pull requests are opened. Configure here.
What
Adds a Mainframe Codex plugin that works identically to the Cursor plugin: the same
share-videoskill, the same hosted MCP wiring, and an AFK stop hook. The change is built DRY on a host-agnostic core shared by both hosts (and ready for a future Claude plugin).How Codex support works (researched from current Codex docs + source)
.codex-plugin/plugin.json(parallel to Cursor's.cursor-plugin/plugin.json). Unknown top-level fields are ignored, so the shared identity metadata is safe to include.Stopevent. The hook reads{ transcript_path, stop_hook_active, ... }on stdin and returns{ "decision": "block", "reason": ... }to continue an away session withreasonas a fresh prompt — the analog of Cursor'sfollowup_message.stop_hook_activeis the loop guard..mcp.jsonparser accepts themcpServers(camelCase) wrapper and remoteurl(streamable HTTP) servers, so Cursor's existing./.mcp.jsonis reused unchanged..agents/plugins/marketplace.jsonexposes the plugin via a Git-URL source rooted at the repo, so the Codex plugin sharesskills/,./.mcp.json,hooks/, anddist/with Cursor instead of duplicating them.Changes
hooks/core/):transcript.tsnow holds only host-agnostic primitives (timestamp + Mainframe-URL parsing, file reading, summary shaping);stop-policy.tsexposesdecideStop(summary, stopTimeMs).hooks/cursor/): transcript parsing moved out ofcore/intocursor/transcript.ts; behavior is unchanged and.cursor-plugin/plugin.jsonis byte-identical.hooks/codex/):transcript.tsparses the Codex rollout JSONL ({ timestamp, type, payload }), recognizingevent_msg/user_messageandresponse_item/function_call, ignoring unrelated event types, and failing closed on corruption or a missingsession_meta.stop-evaluator.ts+stop.ts+hooks.jsonimplement theStopcontract.tooling/generate.tsemits.codex-plugin/plugin.jsonand.agents/plugins/marketplace.jsonfrom the shared metadata; package surface, drift check, release archive, andmark-executablecover both hosts.README.mdandAGENTS.mdupdated for the Cursor + Codex scope.The stop-hook suggestion copy still contains only elapsed hours — never transcript content, prompts, paths, or secrets.
Testing
bun run verify— green (66 tests, typecheck, lint, format, generated-drift, build:check).bun run archive:release— green; the tgz contains both plugins' surfaces.{"decision":"block","reason":"...hours..."}when the user is away with work and no prior share; returns{}for the loop guard (stop_hook_active: true), non-Stopevents, and a null transcript.{"followup_message":"..."}(no regression).I could not exercise a live Codex install in this environment; the manifest, marketplace, hook, and MCP shapes were validated against the current Codex docs and
openai/codexsource, plus strict schema tests over the generated files.