feat: collapse grok-bot-cli into one root Agent Bundle project - #50
Conversation
One dispatcher, one CI gate. The generated `gbot` CLI (`dist/bin/gbot.mjs`) replaces the hand-written `src/cli.js`; the nested private `plugin/` project and its `file:..` hatch are deleted. `gbot-install` binds `runInstallCli` to the same npm `dist/` so Codex, Claude Code, and Cursor install the `grok-bot` MCP tools and `talk-to-grok-bot` skill from one package. - Domain JS moves to `src/core/`; #46 exit-code derivation and failure document flattening live in `src/core/codex/contract.js`, and `sendToCodexThread` / `codexStatus` return documents instead of throwing. - `src/cli/**` routes: bots, groups, doctor, history, send, thread (dedicated CLI routes, not MCP projections) and codex status/list-threads/send/queue. - `send`, `codex send`, and `codex status` use `exitCode: 'result'`: one JSON document on stdout with `exitCode`; refusals keep `reason`/`delivery`. - Gateway-backed routes redact thrown error text before the framework prints it, matching the MCP tools and the old `fail()`. - Breaking (pre-1.0 minor): Node >= 22.19.0, command-local options only, `--json` reserved before `--`, failure JSON on stdout, usage errors exit 2. Supersedes #47.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
🦋 Changeset detectedLatest commit: 46f624b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
🦋 Changeset detectedLatest commit: 7f09275 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7f092755f4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| delivery: out.delivery === 'accepted' ? ('accepted' as const) : ('unknown' as const), | ||
| ...(typeof out.messageId === 'string' ? { messageId: out.messageId } : {}), | ||
| envelopeId: envelope.messageId, | ||
| correlationId: envelope.correlationId, | ||
| ...(input.replyTo ? { replyTo: input.replyTo } : {}), | ||
| hop: envelope.hop, | ||
| maxHops: envelope.maxHops, | ||
| exitCode: 0 as const, |
There was a problem hiding this comment.
Fail sends that receive no gateway receipt
When the gateway returns a successful response without a string messageId, sendPrompt deliberately reports delivery: "unknown", but this route still attaches exitCode: 0. Automation will therefore treat an unacknowledged send as successful instead of checking the thread before resending; derive the exit code from the delivery state and reserve zero for confirmed acceptance.
Useful? React with 👍 / 👎.
| const message = input.message.join(' ').trim(); | ||
| const backend = await openBackendFromInput(input); | ||
| const out = await backend.send(input.target, withEnvelopeHeader(message, envelope)); |
There was a problem hiding this comment.
Reject blank Grok Bot messages
If the positional message consists only of empty or whitespace arguments, joining and trimming produces "", which is then sent to the gateway. The previous dispatcher rejected this case, and the new array schema only requires an element rather than nonblank resulting text; validate the normalized message before opening the backend or sending.
Useful? React with 👍 / 👎.
| out = await sendToCodexThread(input.threadId, input.message.join(' ').trim(), { | ||
| envelope, | ||
| whenBusy: input.whenBusy, | ||
| }); |
There was a problem hiding this comment.
For input such as gbot codex send <thread> " " (or equivalent JSON input), the schema accepts the array and this expression normalizes it to an empty string before starting or queueing a Codex turn. This bypasses the old CLI's nonempty-message check and can create a meaningless empty turn, so reject the normalized message before calling sendToCodexThread.
Useful? React with 👍 / 👎.
- withRedactedErrors keeps the error name and own fields (reason, mode, delivery, envelope) so failure documents still classify; `gbot send` usage refusals report reason "usage" again. - codex list-threads / queue return the flat failure document on stdout (reason "bad-response", "experimental-disabled", ...) instead of letting the framework print a stack trace; shared failureDocumentSchema. - bots list / groups list --json are bare arrays again, as before. - codex queue human output uses the old formatter (formatCodexQueue). - README/changeset: drop the removed --instructions alias, name the on|off narrowing, the `chat` history event, stderr failures for non-send commands, and the gbot vs grok-bot bundle rename. - Tests cover send `--` protection, usage reason, and list-threads bad-response documents.
Summary
One Agent Bundle project at the repository root replaces the two dispatchers that drifted (#24 epic): the hand-written
src/cli.jsand the nested privateplugin/project (file:..+resolve.symlinks: false) are deleted in the same change that makes the generated graph green.gbot/grok-bot→ generateddist/bin/gbot.mjs(plugin.name: 'gbot');gbot-install→ authoredsrc/gbot-install.tsbound to the same npmdist/viarunInstallCli. Source-wrap publish:files: ["dist", …],prepack: agent-bundle prepack,output.distPath: 'artifact'for the host composite.src/core/(moved, not retyped).src/core/codex/contract.jsowns feat(codex): machine-readable contract, delivery envelope, busy-thread refusal (#37 #38 #39) #46 exit-code derivation and failure-document flattening;sendToCodexThread/codexStatusreturn documents instead of throwing.src/cli/**: bots, groups, doctor, history,sendandthread(dedicated CLI routes, not MCP projections),codex status|list-threads|send|queuewithexitCode: 'result'.gbot_send/gbot_threadand thetalk-to-grok-botskill move to the root tree unchanged in behavior.npm run checkgate plus pack → global install →gbot --help/gbot-install --help; the separatepluginjob is gone.Deviations from the synthesis sketch, deliberate: the contract stays JS/JSDoc (
contract.js, notcontract.ts); Codex routes are rendered.tsxwithAgent.Resultrather than plain.ts; gatewaysend/thread/historyare dedicated CLI routes.Breaking (pre-1.0
minor)>=22.19.0(was>=18).gbot send --history-dir DIR …); the leading-global form is gone.--jsonis a reserved framework flag anywhere before--; put--before flag-like message text. Mid-message--jsonas literal text is no longer expressible.send,codex send, andcodex statuswrite one JSON document to stdout withexitCode(failures keeperror,delivery,reason,mode, envelope ids; exit 1). Previously failure JSON went to stderr. Argument/schema errors exit 2 (was 1).Gate (run locally on this branch, which contains
origin/main@ 4b3cd78)npm run check(validate→build→validate --artifact→typecheck→test)npx publint@0.3.24npm pack→ tarball contains onlydist/**,package.json,README.md,CHANGELOG.md,LICENSE→npm install --global --prefix <tmp>→gbot --help,gbot-install --help,gbot doctor --json,gbot send --hop 4 General ack --json(exit 1,reason: "hop-limit"on stdout),gbot codex status --json(exit 0 against a live 0.154.0 daemon)Review of record
Author: Sol worker. Reviewer of record: Fable. Findings fixed on the branch before opening:
src/core/codex/contract.jsshippedcodexEnvFromProcessandCODEX_SEND_REASONSwith no production caller (a second env parser that could drift frombuildEnvelope), plus an@seepointing at a local scratch path. Removed; tests inline the reason list.redactSecretspass the oldfail()and the MCP tools apply.openBackendFromInputnow wraps the backend so every route redacts before the framework writes stderr.gbot send --jsonrefusals (hop-limit, gateway failures) lost their structuredreason/deliverydocument (framework printed a bare message).sendnow uses the same stdout outcome document ascodex send; covered intest/history.test.js..gitignoreduplicated four existing entries.Independent change-risk review (Claude Opus 5, read-only) — dispositions
send/codex sendprinted a raw stack trace and no--jsonfailure document (e.g. documentedreason: "bad-response"forcodex list-threads).codex list-threadsandcodex queuenow return the flat failure document on stdout (reason,exitCode: 1), tested.thread/bots/groups/doctorkeep the framework's stderr message + exit 1; README and changeset now say so. The stack-trace echo itself comes from React Flight's defaultonErrorin the agent-bundle CLI render host (noonErrorpassed) — framework follow-up, not fixable here.withRedactedErrorsrewrapped as bareError, droppingname/reason, sogbot send --jsonlostreason: "usage".nameand own fields; test assertsreason: "usage".--instructionsalias dropped but README documented it;--notify/--hiddennarrowed toon/off.bots list/groups list --jsonbecame{bots:[…]}/{groups:[…]}.grok-bot→gbot; an old source-built install collides on thegrok-botserver.codex queuehuman output was a JSON dump.formatCodexQueuerestores the old layout.gbot sendargv tests;--protection untested.test/history.test.jscovers--protection, hop-limit, gateway failure, and usage documents.codex list-threads/queuegainedexitCodenot named in the changeset.codexcommand.chatalias records historyevent: "thread".npm cidepends on pkg.pr.new preview tarballs.Post-fix gate (46f624b):
npm run checkgreen (143 unit + 13 route tests), publint clean, packed install smoke pass.Relationships
feat/gbot-install-dist), which packaged the nestedplugin/dist for install. This PR deletesplugin/once; feat: ship the grok-bot plugin + gbot-install bin inside the npm package #47 should be closed as superseded when this merges. Not closing it yet.Residual risk
agent-bundle/@agent-bundle/runtimeare devDependencies pinned to pkg.pr.new preview tarballs (@8e55ab832d), as the nestedplugin/already was; the root.npmrc(allow-remote=all) exists for that transitive install. Consumers are unaffected (generateddist/is self-contained), but the repo build depends on those previews staying available until a published agent-bundle release is pinned.gbot bots <command>/codex <command>group lines in--helphave no description (framework group rendering); cosmetic.