feat(grok): add first-class Grok Build integration#81
Conversation
Add Grok Build as a released hcom tool so agents can launch, message, and coordinate with `hcom grok` the same way as Claude/Cursor/Kimi. What works: - `hcom grok` / `hcom grok-build` — PTY launch with hooks install - `hcom hooks add grok` / `remove` — `~/.grok/hooks/hcom.json` - `hcom r` / `hcom f` — resume + fork (`--resume`, `--fork-session`) - Full-body PTY inject delivery (Grok ignores Claude exit-2 Stop) - Claude-compat host detection so dual hooks do not double-queue - Transcript parse of `~/.grok/sessions/**/updates.jsonl` - Config: `grok_args` / `HCOM_GROK_ARGS` Delivery notes (validated end-to-end): - Grok loads Claude settings hooks and strips HCOM_PTY_MODE - Bare `<hcom>` wake + WT/WSL control sequences corrupt the composer - Inject plain-text message body, single Enter, ack immediately - Skip followup_message when the prompt already carries the body Tests: cargo test green for grok hooks, integration_spec, transcript.
Remove a duplicated #[test] attribute, restore #[test] on phase1_timeout_is_ten_seconds, and drop a useless borrow that fails clippy -D warnings on CI.
Grok full-body inject was writing the whole body then force-Entering after a fixed 350ms and acking immediately. On slow WSL/Grok Build the composer often still had not absorbed the paste, so Enter was dropped and the body sat unsent with no retry. - Pace inject in small chunks with short gaps - Scale post-inject settle with payload length (cap 4s) - After first Enter+ack, confirm status left listening; re-Enter up to 3x
commit_delivery_ack sets ST_ACTIVE immediately, which made the post-Enter confirm path believe Grok had submitted even when the body still sat in the composer (UPS only fired after a manual Enter ~25s later). Defer bus ack until real UPS (prompt|trigger) or pending is cleared by the skip-followup hook path; re-Enter while still pending; double-\r burst for slow WSL paste digest.
HCOM_GROK_DELIVERY=wake|full (also ~/.hcom/grok_delivery_mode) selects PTY inject strategy. Wake mode pastes only "hcom: wake" then Enter so GB can queue quickly; bus body is delivered via native UPS followup_message. Claude-compat UPS defers bare-wake delivery to native grok hooks to avoid dual followup races. E2E on headless silo: inject→enter ~1s, model saw BARE-WAKE-TEST payload and replied PONG-BARE.
CI rust-tests/windows-build failed on fmt only (inject_text_paced safe_text chain). No logic change.
|
thanks for the pr looks good. here is AI review may or may not be relevant you can check what you want to do. PR 81 review: Grok Build integrationVerdict: do not merge. The hook delivery path is structurally broken, the PTY path bypasses every safety mechanism hcom has, and content is mutated with no working fallback. The integration scaffolding (registration, launch, lifecycle, resume, transcripts) has value and can merge after targeted fixes, but the delivery architecture needs a redesign before What was reviewedPR 81 at The hook delivery path is broken at the protocol levelObserve-only events cannot deliver messagesGrok classifies hook events by gate type. PR 81 emits message payloads from three observe-only events:
None of these outputs reach the model. Every message that attempts delivery through these hooks is silently lost. Stop uses the wrong output field
struct StopHookJson {
decision: Option<String>,
reason: Option<String>,
continue_: Option<bool>, // serde rename "continue"
stop_reason: Option<String>, // serde rename "stopReason"
hook_specific_output: Option<StopHookSpecificOutputJson>,
}
struct StopHookSpecificOutputJson {
additional_context: Option<String>,
}PR 81's {"followup_message": "..."}
Acknowledgment fires on pipe flush, not model delivery
For observe-only events this is a guaranteed silent loss: hcom marks the message as delivered while Grok drops the output. For Stop, the wrong field means the same outcome: empty parse, no continuation, message lost, cursor advanced. Session-end Stop creates a deterministic loss caseGrok fires a separate session-end Stop when the user exits. PR 81's The PTY full-body path bypasses five safety mechanisms1. Prompt readiness disabledrequire_ready_prompt: false,hcom's ready-prompt check ensures the tool's normal input interface is visible. Disabling it means hcom will inject text into any screen state. 2. Empty prompt check disabledrequire_prompt_empty: false,hcom's empty-prompt check prevents submitting content when the user has an unsent draft. The PR acknowledges that Grok's input box cannot be scraped — but the correct response is "don't inject," not "inject anyway." 3. Ctrl-U without observation
4. Force Enter without ownershipAfter injecting text and sleeping for a length-scaled settle time, hcom presses Enter without verifying that the composer contains exactly the injected text. This is the fundamental ownership check that hcom added and hardened across multiple tools and PRs. Bypassing it means:
5. Force-ack after failed verificationAfter three Enter attempts without confirmation, hcom force-acknowledges the pending batch: // Last resort: clear bus (composer may still need manual Enter).
if let Some(prepared) = prepare_pending_messages(db, ¤t_name) {
commit_delivery_ack(db, &prepared.ack);
}This produces at-most-once delivery under uncertainty: when hcom cannot prove delivery, it discards the messages. This conflicts with hcom's own direction of travel — bounded retries that park delivery while leaving messages pending. Content is mutated and truncated
The code comment says "full body still arrives via UserPromptSubmit hooks when present." But UserPromptSubmit is observe-only — stdout is ignored. There is no working fallback for the truncated or mutated content. Dual hook ownership creates race conditionsGrok loads Claude-compat hooks from
Both hook sets fire for each event. The code tries to coordinate with One owner per tool, not two racing owners with heuristic deduplication. Other defects
|
| Model | What happens |
|---|---|
| Prompt-start hook payload | Claude PTY, Codex, Kimi, and Gemini attach the real body during the same turn started by the PTY trigger |
| Blocking Stop listener | Claude print/vanilla Stop waits for a message and then resumes Claude with the body; no PTY wake |
| Plugin/extension API | OpenCode, Kilo, Pi, and OMP call a host prompt/message API directly |
| Manual retrieval | The agent runs hcom listen without an automatic wake path |
Merge recommendation
Merge after fixes
| Component | Fix needed |
|---|---|
Tool::Grok registration, aliases, config, help |
None |
| Hook installation/removal | Resolve $GROK_HOME consistently |
| Launch and lifecycle hooks (SessionStart, SessionEnd, PreToolUse) | Remove delivery/bootstrap from SessionStart stdout |
| Status tracking and TUI integration | None |
| Resume/fork | Fix arg handling: preserve unknown flags verbatim, reject -p/--single |
| Transcript parser | Remove chunk trimming; add real fixtures with chunk boundaries |
Remove or replace
| Component | Reason |
|---|---|
| PostToolUse message delivery | Observe-only; stdout ignored |
| UserPromptSubmit message delivery | Observe-only; stdout ignored |
followup_message from Stop |
Not in StopHookJson schema |
| Current full-body PTY implementation | Content mutation, no ownership, no verification |
sanitize_grok_inject_text |
Semantic corruption with no working fallback |
| Ctrl-U / force-Enter / double-Enter | Unsafe without composer parser |
| Force-ack after failed delivery | Violates pending-message invariant |
Grok-specific delivery logic in claude.rs |
One delivery owner per tool |
prompt_already_carries_hcom_body |
Heuristic deduplication for a race that shouldn't exist |
Current GrokDeliveryMode / HCOM_GROK_DELIVERY paths |
They select delivery paths that are not protocol-correct as implemented |
Delivery choices requiring implementation work
| Choice | Missing implementation |
|---|---|
| Two-stage sentinel delivery | Add a Grok composer parser and safe <hcom> submission for idle wake; deliver the real body only from genuine Stop(reason=="end_turn") using Grok's supported continuation schema |
| Active Stop delivery | Leave messages pending until genuine Stop, obey Stop context/continuation limits, and acknowledge only after the supported handoff |
| Historical-Codex-style retrieval | Add a non-destructive read plus exact batch acknowledgment after the tool result is observed |
| Full-text PTY prompt | Add lossless paste, exact composer parsing, post-paste ownership verification, submission correlation, and failure-safe acknowledgment |
| ACP | Define whether hcom creates a separate managed Grok session or can attach to an existing interactive session |
| Bootstrap | Use --rules, --append-system-prompt, or another supported launch-time channel rather than SessionStart stdout |
Owner review (PR aannoo#81): observe-only hooks discard stdout; Stop only accepts hookSpecificOutput.additionalContext (not followup_message). - SessionStart/UPS/PostToolUse: status only — no message payloads/acks - Stop: deliver pending via additionalContext on genuine end_turn only; skip session-end reasons (channel_closed/shutdown/…) - Claude-compat: no Grok delivery ownership (native grok-stop only) - PTY: short wake sentinel only; no full-body sanitize/Ctrl-U/force-ack - Respect $GROK_HOME; resume VALUE_FLAGS for worktree/debug/system-prompt - Transcript: do not trim stream chunks; surface tool is_error - Help: -p is one-shot, not multi-turn hcom agent
Empty commit so PR aannoo#81 CI re-runs on the current delivery protocol tip.
Summary
Adds first-class Grok Build (
grok) support to hcom: hook install, PTY launch/resume/fork, message delivery, transcript parsing, config, docs, and focused tests.Validated on WSL/Windows Terminal against Grok Build (hooks + PTY + multi-agent send/reply).
Delivery protocol (aligned with Grok StopHookJson)
Grok discards stdout on observe-only hooks (SessionStart / UserPromptSubmit / PostToolUse). The only reliable model-injection gate is Stop →
hookSpecificOutput.additionalContext.Key rules after owner review alignment (
d482ea5):additionalContext(+ snake_case twin); neverfollowup_messagechannel_closed, shutdown, …)$GROK_HOMEfor hooks path; dual UPS races avoided by keeping pending until StopWhat works
hcom grok/hcom grok-build— launch with hooks installhcom hooks add grok/hcom hooks remove grok—~/.grok/hooks/hcom.jsonhcom r <name>/hcom f <name>—--resume+--fork-sessionhcom transcriptvia~/.grok/sessions/**/updates.jsonlNew files
src/hooks/grok.rs— hook install/verify/remove + lifecycle (Stop gate)src/transcript/grok.rs—updates.jsonlparserTesting
Automated
cargo test grok— hooks, Stop additionalContext shape, host detection, delivery wake testscargo fmt/ clippy cleanups on the branchManual (author, home/
~workspace)hcom: wake)hcom grok-stopwith pending → stdouthookSpecificOutput.additionalContext+ loggrok.stop.additional_context+ cursor advancePONG-PROTOack (agent also may self-pull viahcom listen, which advances cursor; pure mid-turn Stop body inject depends on ending turn without listen)Usage
Known limits / follow-ups
Nonefor Grok (wake uses force-Enter after sentinel; no Ctrl-U)--rules/ skill), not hook stdouthcom listenmid-turn advancelast_event_idand can skip the Stop additionalContext path (listen is intentional CLI consumption)Offline patch kit
~/workspaces/hcom-patches/grok-build-integrationtracks tipd482ea5for brew/offline apply.