chore: promote dev → main (omni serve Model A) - #2516
Conversation
…odel A) Enhance the `genie omni serve` inbound one-shot into "Model A": - defaultSpawnClaude now runs `claude -p --output-format stream-json --verbose --session-id <uuid> [--append-system-prompt-file <persona>]`, parsing the final reply out of the NDJSON stream (terminal result event → assistant text deltas → raw stdout fallback). Arg construction + parsing are extracted into pure, exported buildClaudeArgs / extractStreamJsonReply. - SpawnClaudeOpts gains optional personaFile + sessionId; the runner resolves personaFile = route.persona ?? <repo>/AGENTS.md and derives a STABLE deterministicSessionId(instance, chat) so a conversation resumes across messages. - Route the inbound WhatsApp stanza id (messageId) through handleMessage → startRoutedRun → runOneShot and set a ⏳→✅/❌ status reaction on it, route-scoped to (route.instance, route.chat). Generalizes the existing approval-scoped emitStatusReaction into a shared emitReaction seam; the route ack records no glyph and skips the reconciliation guard, stays fire-and-forget (drained by whenIdle), and never throws. - Extend the OmniRoute config (schema + runtime type) with optional persona. The approval flow is untouched (same ⏳/✅/❌ behaviour, glyph recording and reconciliation). Adds 12 tests covering argv, stream-json parsing, session-id stability, route-scoped ⏳→✅/❌ acks, the no-messageId no-op, and persona resolution — all with injected spawnClaude + setReaction, zero fork/HTTP.
…odel A review)
Address the BLOCKED review of the Model A one-shot:
- CRITICAL — `claude --session-id <id>` CREATES a session and exits 1 "already
in use" on every turn after the first, so multi-turn was broken. Add a
resume-first orchestration (`runClaudeSession`): attempt `--resume <id>` first
(one spawn for turn 2..N and across `omni serve` restarts, since the session
persists on disk) and fall back to `--session-id <id>` only when the session
is missing. Verified LIVE against claude 2.1.201 — the missing-session error is
actually "No conversation found with session ID" (NOT the "not found" the
review assumed), so the detection regex was broadened accordingly. Extracted a
testable `RawClaudeSpawn` seam so the resume/create branching is unit-tested
without a fork; `buildClaudeArgs` gains a `create | resume` mode.
- MEDIUM — `extractStreamJsonReply` ignored `is_error`: an error/empty terminal
result returned the raw NDJSON blob, published as a ✅ reply. It now returns
`{ reply, isError }`; a soft-error (is_error / non-success subtype / empty
result) sets `isError`, and `runOneShot` treats `(exitCode !== 0 || isError)`
as failure → error notice + ❌, never the raw blob. `SpawnClaudeResult` gains
`isError?`.
- LOW — `resolvePersonaFile` now existsSync-checks an explicit `route.persona`
too (a typo'd path would make claude exit 1 every run); a missing path is
logged and dropped.
Also close stdin (`ignore`) in the default fork so claude doesn't wait ~3s for
piped input. `bun run check` green (+9 tests: resume-first create/resume/no-
fallback/already-in-use regression, error+empty result parsing, soft-error ack,
missing-persona drop). Live end-to-end: turn 1 creates ("OK"), turn 2 resumes
and recalls context ("Quokka").
The bare `|not found` alternative matched ANY "not found" stderr (e.g. a "model not found" / MCP "… not found") on a turn whose session actually exists, triggering a spurious `--session-id` create that then errors "already in use" (one wasted spawn + a worse error). Each remaining alternative is session-scoped: `no conversation found` (claude 2.1.201), `no such session` / `session not found` (legacy phrasings). Add a test asserting a generic "… not found" resume failure is NOT treated as a missing session (resume-only, no create fallback).
…names
The route ⏳→✅ ack (and approval acks) POSTed reactions to /api/v2/messages
with {chatId, reaction}, but omni's send endpoint rejects that (400). Omni's
dedicated reaction route is POST /api/v2/messages/send/reaction expecting
{instanceId, to, messageId, emoji}. Corrected the endpoint + field names.
feat(omni): Model A — persona + session + streaming + ⏳→✅ ack for genie omni serve
|
Warning Review limit reached
Next review available in: 7 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (41)
📝 WalkthroughWalkthroughThis PR upgrades Omni's one-shot Claude execution with persona injection, deterministic resumable sessions, stream-json reply parsing, and route-scoped reaction acks; adds a new Hermes Genie native plugin surface with bridging, commands, hooks, schemas, docs, skills, tests; and bumps release versions across manifests. ChangesOmni Model A
Estimated code review effort: 4 (Complex) | ~60 minutes Hermes Genie native surface
Estimated code review effort: 4 (Complex) | ~75 minutes Version sync
Estimated code review effort: 1 (Trivial) | ~2 minutes Sequence Diagram(s)sequenceDiagram
participant WhatsApp as handleMessage
participant Route as startRoutedRun
participant OneShot as runOneShot
participant Session as runClaudeSession
participant Claude as claude CLI
participant API as messages/send/reaction
WhatsApp->>Route: msg.messageId
Route->>OneShot: messageId
OneShot->>API: emit ⏳ before spawn
OneShot->>Session: sessionId, personaFile
Session->>Claude: --resume sessionId
alt missing session
Session->>Claude: retry with --session-id
end
Claude-->>Session: stream-json NDJSON
Session-->>OneShot: stdout, exitCode, isError
alt success
OneShot->>API: emit ✅
else failure or timeout
OneShot->>API: emit ❌
end
sequenceDiagram
participant Hermes as commands.py / CLI
participant Bridge as genie_bridge.py
participant Genie as genie CLI
Hermes->>Bridge: tool args and cwd
Bridge->>Genie: argv list via subprocess.run
Genie-->>Bridge: stdout/stderr
Bridge-->>Hermes: structured JSON envelope
Hermes->>Hermes: render outcome-first output
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 63015670d9
ℹ️ 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".
| ); | ||
| publish(replySubject, buildRoutedReplyPayload(route.instance, route.chat, content, genId(), now())); | ||
| // ✅ once a genuine reply is published; ❌ on a non-zero exit or soft error. | ||
| emitRouteReaction(route, messageId, ok ? STATUS_APPROVED : STATUS_DENIED); |
There was a problem hiding this comment.
Serialize route status reactions
When the Claude turn is fast and the initial ⏳ HTTP call is still pending, this second emitRouteReaction starts another fire-and-forget HTTP request without waiting for the first. Because route acks use no guard or ordering, the API can process ✅/❌ before ⏳ and then have the slower ⏳ complete last, leaving a successful routed run marked pending on the inbound WhatsApp message. Please serialize route acks for a given message or wait for the pending reaction before sending the terminal one.
Useful? React with 👍 / 👎.
| // Thread the inbound WhatsApp stanza id so the run can ⏳→✅/❌ react on it. | ||
| const route = findRoute(instance, chat); | ||
| if (route) startRoutedRun(route, inboundId, body); | ||
| if (route) startRoutedRun(route, inboundId, body, msg.messageId); |
There was a problem hiding this comment.
Skip route acks for reaction frames
This passes msg.messageId to the route ack for every routed inbound before the later parseReaction check. For reaction frames, this field is the reacted-to message id rather than a new inbound text stanza, so a user reaction in a routed chat makes the run status mutate the message being reacted to (and can interfere with approval status glyphs if that chat is also routed). Gate routed runs/acks to non-reaction messages or avoid using msg.messageId for reaction payloads.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Code Review
This pull request introduces route-scoped run acknowledgments (using hourglass, check, and cross emojis), stable session continuity (via resume-first session threading), and support for custom route personas in the Omni runner. Key changes include parsing Claude's stream-json output, deriving deterministic session IDs, and updating the reaction API endpoint. The review feedback suggests several robustness improvements: adding a -- argument separator to prevent messages starting with hyphens from being parsed as CLI options, adding an early abort signal check in runClaudeSession to avoid spawning processes unnecessarily, and enhancing error notices by appending stdout context on non-zero exit codes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| return [ | ||
| '-p', | ||
| '--output-format', | ||
| 'stream-json', | ||
| '--verbose', | ||
| ...sessionFlag, | ||
| ...(opts.personaFile ? ['--append-system-prompt-file', opts.personaFile] : []), | ||
| opts.message, | ||
| ]; |
There was a problem hiding this comment.
If opts.message starts with a hyphen (e.g., --help or -v), it can be misinterpreted by the claude CLI as an option rather than the positional prompt argument. Inserting the standard -- argument separator before opts.message ensures that any user message starting with a hyphen is correctly treated as the prompt.
return [
'-p',
'--output-format',
'stream-json',
'--verbose',
...sessionFlag,
...(opts.personaFile ? ['--append-system-prompt-file', opts.personaFile] : []),
'--',
opts.message,
];| export async function runClaudeSession(opts: SpawnClaudeOpts, rawSpawn: RawClaudeSpawn): Promise<SpawnClaudeResult> { | ||
| const base = { message: opts.message, sessionId: opts.sessionId ?? randomUUID(), personaFile: opts.personaFile }; | ||
| const spawnOpts = { cwd: opts.cwd, signal: opts.signal }; | ||
| const resumed = await rawSpawn(buildClaudeArgs({ ...base, mode: 'resume' }), spawnOpts); | ||
| if (resumed.exitCode === 0 || opts.signal.aborted || !NO_SESSION_RE.test(resumed.stderr)) { | ||
| return toSpawnResult(resumed); | ||
| } | ||
| // Session does not exist yet → create it (this spawn processes the message). | ||
| const created = await rawSpawn(buildClaudeArgs({ ...base, mode: 'create' }), spawnOpts); | ||
| return toSpawnResult(created); | ||
| } |
There was a problem hiding this comment.
If the abort signal is already aborted when runClaudeSession is called, we should avoid spawning any child processes. Adding an early guard check for opts.signal.aborted prevents unnecessary process spawning and potential unhandled errors.
| export async function runClaudeSession(opts: SpawnClaudeOpts, rawSpawn: RawClaudeSpawn): Promise<SpawnClaudeResult> { | |
| const base = { message: opts.message, sessionId: opts.sessionId ?? randomUUID(), personaFile: opts.personaFile }; | |
| const spawnOpts = { cwd: opts.cwd, signal: opts.signal }; | |
| const resumed = await rawSpawn(buildClaudeArgs({ ...base, mode: 'resume' }), spawnOpts); | |
| if (resumed.exitCode === 0 || opts.signal.aborted || !NO_SESSION_RE.test(resumed.stderr)) { | |
| return toSpawnResult(resumed); | |
| } | |
| // Session does not exist yet → create it (this spawn processes the message). | |
| const created = await rawSpawn(buildClaudeArgs({ ...base, mode: 'create' }), spawnOpts); | |
| return toSpawnResult(created); | |
| } | |
| export async function runClaudeSession(opts: SpawnClaudeOpts, rawSpawn: RawClaudeSpawn): Promise<SpawnClaudeResult> { | |
| if (opts.signal.aborted) { | |
| return { stdout: '', exitCode: -1, isError: true }; | |
| } | |
| const base = { message: opts.message, sessionId: opts.sessionId ?? randomUUID(), personaFile: opts.personaFile }; | |
| const spawnOpts = { cwd: opts.cwd, signal: opts.signal }; | |
| const resumed = await rawSpawn(buildClaudeArgs({ ...base, mode: 'resume' }), spawnOpts); | |
| if (resumed.exitCode === 0 || opts.signal.aborted || !NO_SESSION_RE.test(resumed.stderr)) { | |
| return toSpawnResult(resumed); | |
| } | |
| // Session does not exist yet → create it (this spawn processes the message). | |
| const created = await rawSpawn(buildClaudeArgs({ ...base, mode: 'create' }), spawnOpts); | |
| return toSpawnResult(created); | |
| } |
| const content = ok | ||
| ? truncateReply(result.stdout, maxReplyChars) | ||
| : errorNotice( | ||
| result.exitCode !== 0 ? `exit code ${result.exitCode}` : result.stdout || 'agent returned an error', | ||
| ); |
There was a problem hiding this comment.
When the process exits with a non-zero code, the error notice currently only displays the exit code, completely ignoring result.stdout. Since result.stdout contains the parsed output/error message from Claude Code's stream-json, appending it to the error notice when available provides much more helpful context for debugging.
| const content = ok | |
| ? truncateReply(result.stdout, maxReplyChars) | |
| : errorNotice( | |
| result.exitCode !== 0 ? `exit code ${result.exitCode}` : result.stdout || 'agent returned an error', | |
| ); | |
| const content = ok | |
| ? truncateReply(result.stdout, maxReplyChars) | |
| : errorNotice( | |
| result.exitCode !== 0 | |
| ? 'exit code ' + result.exitCode + (result.stdout ? ': ' + result.stdout : '') | |
| : result.stdout || 'agent returned an error', | |
| ); |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/omni-runner.test.ts`:
- Around line 971-1041: The tmpdir fixture cleanup in these `threadingRunner`
tests is duplicated with per-test try/finally blocks instead of using the shared
`afterEach` cleanup pattern. Update the three tests around `threadingRunner`,
`mkdtempSync`, and `rmSync` to use the existing `makeTmpDir(...)` helper (or
equivalent shared fixture setup) and centralize directory cleanup in
`afterEach`, removing the repeated per-test teardown logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5031f05b-bd7f-4088-933a-139f01da5bd7
📒 Files selected for processing (8)
.claude-plugin/marketplace.jsonpackage.jsonplugins/genie/.claude-plugin/plugin.jsonplugins/genie/package.jsonsrc/lib/omni-config.tssrc/lib/omni-runner.test.tssrc/lib/omni-runner.tssrc/types/genie-config.ts
| test('threads a stable session id (same across messages) and the explicit route persona', async () => { | ||
| const dir = mkdtempSync(join(tmpdir(), 'genie-omni-explicit-')); | ||
| try { | ||
| const persona = join(dir, 'persona-A.md'); | ||
| writeFileSync(persona, '# persona A'); | ||
| const db = freshDb(); | ||
| const seen: SeenSpawn[] = []; | ||
| const runner = threadingRunner(db, seen, [{ instance: INSTANCE, chat: ROUTE_CHAT, repo: ROUTE_REPO, persona }]); | ||
|
|
||
| runner.handleMessage(...mappedInboundWithId('m1', 'id1')); | ||
| await runner.whenIdle(); | ||
| runner.handleMessage(...mappedInboundWithId('m2', 'id2')); | ||
| await runner.whenIdle(); | ||
|
|
||
| expect(seen.length).toBe(2); | ||
| expect(seen[0].personaFile).toBe(persona); | ||
| expect(seen[0].sessionId).toBe(deterministicSessionId(INSTANCE, ROUTE_CHAT)); | ||
| // Stable session id ⇒ the conversation resumes across messages. | ||
| expect(seen[1].sessionId).toBe(seen[0].sessionId); | ||
| } finally { | ||
| rmSync(dir, { recursive: true, force: true }); | ||
| } | ||
| }); | ||
|
|
||
| test('drops a typo’d explicit route.persona that does not exist (never passes it to claude)', async () => { | ||
| const db = freshDb(); | ||
| const seen: SeenSpawn[] = []; | ||
| const missing = join(tmpdir(), 'genie-omni-does-not-exist', 'persona.md'); | ||
| const runner = threadingRunner(db, seen, [ | ||
| { instance: INSTANCE, chat: ROUTE_CHAT, repo: ROUTE_REPO, persona: missing }, | ||
| ]); | ||
|
|
||
| runner.handleMessage(...mappedInboundWithId('m', 'id')); | ||
| await runner.whenIdle(); | ||
|
|
||
| expect(seen[0].personaFile).toBeUndefined(); | ||
| }); | ||
|
|
||
| test('falls back to <repo>/AGENTS.md when route.persona is unset', async () => { | ||
| const dir = mkdtempSync(join(tmpdir(), 'genie-omni-persona-')); | ||
| try { | ||
| writeFileSync(join(dir, 'AGENTS.md'), '# persona'); | ||
| const db = freshDb(); | ||
| const seen: SeenSpawn[] = []; | ||
| const runner = threadingRunner(db, seen, [{ instance: INSTANCE, chat: ROUTE_CHAT, repo: dir }]); | ||
|
|
||
| runner.handleMessage(...mappedInboundWithId('m', 'id')); | ||
| await runner.whenIdle(); | ||
|
|
||
| expect(seen[0].personaFile).toBe(join(dir, 'AGENTS.md')); | ||
| } finally { | ||
| rmSync(dir, { recursive: true, force: true }); | ||
| } | ||
| }); | ||
|
|
||
| test('resolves no persona when neither route.persona nor <repo>/AGENTS.md exists', async () => { | ||
| const dir = mkdtempSync(join(tmpdir(), 'genie-omni-nopersona-')); | ||
| try { | ||
| const db = freshDb(); | ||
| const seen: SeenSpawn[] = []; | ||
| const runner = threadingRunner(db, seen, [{ instance: INSTANCE, chat: ROUTE_CHAT, repo: dir }]); | ||
|
|
||
| runner.handleMessage(...mappedInboundWithId('m', 'id')); | ||
| await runner.whenIdle(); | ||
|
|
||
| expect(seen[0].personaFile).toBeUndefined(); | ||
| } finally { | ||
| rmSync(dir, { recursive: true, force: true }); | ||
| } | ||
| }); | ||
| }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Use afterEach for tmpdir cleanup instead of per-test try/finally.
Three tests each manually mkdtempSync/rmSync with a try/finally block. This duplicates boilerplate and diverges from the stated pattern of centralizing fixture cleanup in afterEach.
♻️ Suggested consolidation
+let tmpDirs: string[] = [];
+afterEach(() => {
+ for (const dir of tmpDirs) rmSync(dir, { recursive: true, force: true });
+ tmpDirs = [];
+});
+function makeTmpDir(prefix: string): string {
+ const dir = mkdtempSync(join(tmpdir(), prefix));
+ tmpDirs.push(dir);
+ return dir;
+}
Then each test drops its own try { ... } finally { rmSync(...) } wrapper in favor of makeTmpDir(...).
As per path instructions, **/*.test.{ts,tsx,js,jsx} files should "Use tmpdir with cleanup in afterEach for test fixtures."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/omni-runner.test.ts` around lines 971 - 1041, The tmpdir fixture
cleanup in these `threadingRunner` tests is duplicated with per-test try/finally
blocks instead of using the shared `afterEach` cleanup pattern. Update the three
tests around `threadingRunner`, `mkdtempSync`, and `rmSync` to use the existing
`makeTmpDir(...)` helper (or equivalent shared fixture setup) and centralize
directory cleanup in `afterEach`, removing the repeated per-test teardown logic.
Source: Path instructions
… seed from main The profile seed (profiles/hermes/genie/, commit b112309) exists only on main lineage; ported verbatim so Group 3 can modify the README on this dev-cut branch. The dev->main merge will reconcile on identical blobs except the README, which carries the wish's additive edit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BEJCsZTjxLyrM8BQKjGEVs
…only tools
Hermes-native surface for Genie (wish hermes-khaw-native-surface, Group 1).
7 read-only tools grounded on the v5 CLI (doctor/board/task list/task
status/launch --dry-run), argv-only subprocess bridge with shell-metachar
rejection, validate_ref traversal guard + in-bounds WISH.md read, uniform
{success, mutation:none, cwd, command|source} payload. 28 pytest cases.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BEJCsZTjxLyrM8BQKjGEVs
…HIP) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014SwFHxrsQVdwG32ChnsKku
…scripts, profile seed update Group 3 of wish hermes-khaw-native-surface. Install script (symlink default, --copy mode), smoke script, native-surface + mutation-gates references, Hermes-native cross-links in root README, Claude Code plugin README, and the Hermes profile seed (additive). Two review LOWs applied: ln -sfn manual one-liner, payload-contract wording. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BEJCsZTjxLyrM8BQKjGEVs
… CLI tree, skills Group 2 of wish hermes-khaw-native-surface. /genie dispatcher (+4 wrapper commands) with outcome-first rendering and evidence footers, advisory hooks (session-start .genie reminder, terminal-scrape advice, never blocking), hasattr-guarded CLI tree and 4 path-based skills. 18 new tests (46 total). Two review LOWs applied: on_session_start degrades on unresolvable cwd; list-shaped command events joined before advisory match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BEJCsZTjxLyrM8BQKjGEVs
…sh skills-fable5-revamp G1) 803->48 and 627->47 lines; optimizer prompt extracted verbatim to prompts/optimizer.md (runtime-read at dispatch); hack catalog moved to references/ with all 8 hacks preserved and re-grounded in the live v5 CLI; skills-lint:ignore marker removed, lint green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014SwFHxrsQVdwG32ChnsKku
Wish skills-fable5-revamp. 396->108, 196->103, 259->107, 282->101; daemon-era team/agent/events flows rewritten to native-team dispatch (Agent tool + SendMessage) and the task DB; grounded-progress clauses added to pm/dream/report; mode contracts extracted to references/; 5 lint markers removed, lint green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014SwFHxrsQVdwG32ChnsKku
Wish skills-fable5-revamp. brainstorm 230->125, wish 105->71, work 181->103, review 171->114, fix 112->75, trace 109->54; all 8 frozen handoff contracts preserved verbatim (template cp rule, wishes:lint gate, task linkage, verdict vocabulary, artifact paths, reviewer!=engineer, session-close outcome words, native-team dispatch); 2 lint markers removed and refs fixed at the root; lint green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014SwFHxrsQVdwG32ChnsKku
Wish skills-fable5-revamp. genie 186->94 (25 intents -> 27 routes, zero dropped), wizard 161->57, learn 108->61, docs 83->43, omni 164->74; lifecycle.md re-grounded to v5 zero-daemon; genie-omni wiring rebuilt from source (omni-config.ts routes contract, ed25519 handshake); two baseline factual errors fixed (orchestration-guard nudges not blocks, genie task status exists); 5 lint markers removed, lint green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014SwFHxrsQVdwG32ChnsKku
…urface feat: Hermes-native plugin surface for Genie
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/hermes-genie/references/native-surface.md`:
- Line 3: The pinned Genie CLI version in the Hermes native-surface reference is
out of date. Update the version mention in native-surface.md so it matches the
new repo/plugin release (the note currently references genie 5.260703.5, but the
PR updates to 5.260704.2); keep the wording aligned with the existing “Grounded
against the genie v5 CLI” note and adjust only the version string in that
documentation block.
In `@plugins/hermes-genie/scripts/install-local.sh`:
- Around line 44-47: The install-local.sh cleanup step unconditionally removes
$target, which can wipe an unrelated real directory instead of only replacing a
previous plugin link. Update the logic around the mkdir/rm block to detect
whether $target is an expected symlink or install-owned plugin directory before
deleting it, and refuse or warn when it points to pre-existing non-plugin
content. Make sure the guard is handled in the path that prepares $target and is
adjusted for the --copy mode behavior where a real directory is intentionally
recreated.
In `@profiles/hermes/genie/README.md`:
- Around line 11-19: The bootstrap seed list and copy step are inconsistent:
`CLAUDE_CODE_PILOT.md` is documented as included but is not actually copied, and
the `hermes profile create` path is not fail-fast because `|| true` suppresses
errors. Update the bootstrap instructions in `README.md` so the file copy step
includes `CLAUDE_CODE_PILOT.md`, and remove the error suppression so failures
from `hermes profile create` surface immediately; keep the wording aligned with
the seed list and the bootstrap section that describes the profile creation
flow.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9fc7409c-4735-4ceb-939d-c3767cf63f90
⛔ Files ignored due to path filters (1)
README.mdis excluded by!*.md
📒 Files selected for processing (32)
.claude-plugin/marketplace.json.genie/wishes/hermes-khaw-native-surface/WISH.mdpackage.jsonplugins/genie/.claude-plugin/plugin.jsonplugins/genie/README.mdplugins/genie/package.jsonplugins/hermes-genie/.gitignoreplugins/hermes-genie/README.mdplugins/hermes-genie/__init__.pyplugins/hermes-genie/commands.pyplugins/hermes-genie/genie_bridge.pyplugins/hermes-genie/hooks.pyplugins/hermes-genie/plugin.yamlplugins/hermes-genie/references/mutation-gates.mdplugins/hermes-genie/references/native-surface.mdplugins/hermes-genie/schemas.pyplugins/hermes-genie/scripts/install-local.shplugins/hermes-genie/scripts/smoke.shplugins/hermes-genie/skills/genie-khaw-bridge/SKILL.mdplugins/hermes-genie/skills/genie-review/SKILL.mdplugins/hermes-genie/skills/genie-work/SKILL.mdplugins/hermes-genie/skills/genie/SKILL.mdplugins/hermes-genie/tests/test_commands.pyplugins/hermes-genie/tests/test_genie_bridge.pyplugins/hermes-genie/tests/test_plugin_contract.pyprofiles/hermes/genie/AGENTS.mdprofiles/hermes/genie/CLAUDE_CODE_PILOT.mdprofiles/hermes/genie/HEARTBEAT.mdprofiles/hermes/genie/README.mdprofiles/hermes/genie/SOUL.mdprofiles/hermes/genie/agent.yamlprofiles/hermes/genie/hermes-profile.yaml
| @@ -0,0 +1,63 @@ | |||
| # Native Surface Reference | |||
|
|
|||
| What the Genie Hermes plugin exposes, layer by layer, and the exact contract every layer honors. Grounded against the genie v5 CLI (flags verified 2026-07-04 on genie 5.260703.5). | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n '5\.26070' package.json plugins/genie/package.json plugins/genie/.claude-plugin/plugin.json .claude-plugin/marketplace.json plugins/hermes-genie/references/native-surface.md
Repository: automagik-dev/genie
Length of output: 623
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Version fields ==\n'
sed -n '1,40p' package.json
printf '\n== plugins/genie/package.json ==\n'
sed -n '1,40p' plugins/genie/package.json
printf '\n== plugins/genie/.claude-plugin/plugin.json ==\n'
sed -n '1,60p' plugins/genie/.claude-plugin/plugin.json
printf '\n== .claude-plugin/marketplace.json ==\n'
sed -n '1,80p' .claude-plugin/marketplace.json
printf '\n== References to 5.260703.5 / 5.260704.2 ==\n'
rg -n '5\.260703\.5|5\.260704\.2|genie v5 CLI|flags verified 2026-07-04' .
Repository: automagik-dev/genie
Length of output: 4203
Update the pinned genie CLI version in this note. plugins/hermes-genie/references/native-surface.md:3 still cites genie 5.260703.5, while this PR bumps the repo/plugin version to 5.260704.2; align the reference so it doesn’t drift on merge.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/hermes-genie/references/native-surface.md` at line 3, The pinned
Genie CLI version in the Hermes native-surface reference is out of date. Update
the version mention in native-surface.md so it matches the new repo/plugin
release (the note currently references genie 5.260703.5, but the PR updates to
5.260704.2); keep the wording aligned with the existing “Grounded against the
genie v5 CLI” note and adjust only the version string in that documentation
block.
Independent review record (pre-merge)Two independent code reviews were commissioned on the full dev→main diff (per promotion protocol), plus adversarial verification of all 6 existing bot findings. Reviewer A (correctness/security): MERGE-WITH-FOLLOW-UPS. All 6 bot findings verified against actual code — none refuted, none release-blocking. Priority follow-ups (all in omni-serve Model A, opt-in
Security pass on Reviewer B (release-safety/merge-integrity): MERGE. Proven: exactly one conflict ( Merge plan: manual merge of dev into main resolving the single README conflict to dev's superset blob (keeps this PR's stated no-resync contract — nothing back-merged into dev). merge-tree re-verified at live tips before pushing. 🤖 Generated with Claude Code |
Performs the dev<->main resync deferred in #2516 so the rolling PR merges conflict-free. Sole conflict (profiles/hermes/genie/README.md add/add) resolved to dev's blob — a byte-exact superset of main's seed (+10 lines, proven by independent merge-integrity review on #2516). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BEJCsZTjxLyrM8BQKjGEVs # Conflicts: # profiles/hermes/genie/README.md
- native-surface.md: version-agnostic grounding line (contract tests pin the flag surface) instead of a stale exact-version pin - install-local.sh: guard rm -rf — only replace a symlink or a dir that looks like a previous plugin install (plugin.yaml present); refuse and exit 1 otherwise (verified: refusal preserves unrelated content) - profiles/hermes/genie/README.md (pre-existing seed content surfaced by the add/add resolution): bootstrap copies CLAUDE_CODE_PILOT.md as documented and no longer hides 'hermes profile create' failures Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BEJCsZTjxLyrM8BQKjGEVs
Ready for human merge (§19)All three CodeRabbit actionable comments are addressed in ab4c8a8:
The earlier codex/gemini re-reviews (19:53Z) restate the omni-runner findings already triaged in the independent-review record above — follow-ups, none blocking. State: MERGEABLE / CLEAN, all checks green. The dev↔main resync deferred in the PR description is done (a547f51) — this merge now only adds dev's commits to main, and dev already contains main. Agent policy (§19) reserves main merges for humans, so this PR now awaits the button. Reminder from the release-safety review: merging re-dispatches the release pipeline toward the stable channel — the designed consequence of promoting to main. Full trail: wish 🤖 Generated with Claude Code |
Wish skills-fable5-revamp. New legacy-v4 module: shared path manifest, detectV4Install(), exported cleanupV4() — content-marker gated rules-file removal (backup-first to ~/.genie/state-backups/v4-cleanup-<ts>/, logged, idempotent), orphaned automagik/genie/4.* cache cleanup (.orphaned_at gated, manifest backup). Wired into a recreated thin 'genie install' finisher (bootstrap handoff restored, non-exec, --skip-v4-cleanup) and guarded post-delivery in 'genie update' so upgraded machines clean too. uninstall.ts consumes the shared manifest. 16 pgserve-free tests incl. backup-before-delete and degradation locks; wish doc re-grounded to dev reality; v4-footprint inventory in reports/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014SwFHxrsQVdwG32ChnsKku
All 10 Success Criteria PASS with pasted evidence: combined surface 5514 -> 2016 lines (-63.4%), skills:lint 118 dead refs -> 0 with zero ignore markers, budgets held, frozen contracts quoted, zero D/R in either repo diff, G8 gates green. Conventions live-CLI list gains 'install' (recreated by G8). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014SwFHxrsQVdwG32ChnsKku
CI runners don't ship the omni binary; the hard exit(2) in getOmniCommands() was masked pre-revamp because every omni-referencing skill hid behind a skills-lint:ignore marker. Default now warns and skips ONLY omni validation (genie checks stay strict) with an honest 'omni checks skipped' suffix; SKILLS_LINT_REQUIRE_OMNI=1 restores the strict behavior. Wish skills-fable5-revamp (adjudicated PR-gate fix). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014SwFHxrsQVdwG32ChnsKku
Wish skills-fable5-revamp — genie skills + v4 trash cleaning. All checks green after skills-lint graceful-degradation fix; 7 independent group reviews SHIP; G7 verification all-10-SC PASS (.genie/wishes/skills-fable5-revamp/verification.md)
|
Now included in this promotion: wish
🤖 Generated with Claude Code |
Closes the three findings confirmed by the independent reviews on #2516: - Reaction frames (and blank bodies) in a route-mapped chat no longer spawn a claude run, publish a reply, or mutate the reacted-to message's status ack — they are stored to the inbox only. The approval-chat reaction path is untouched, including when the route chat IS the approval chat (new coexistence test). - buildClaudeArgs inserts '--' before the message positional so a hyphen-leading message is always a prompt, never parsed as a flag (verified live against the claude CLI). - Non-zero exits now surface a bounded tail of the child's stderr (fallback stdout) in the error notice instead of a bare exit code; SpawnClaudeResult carries stderr through the existing drain pattern. 52 tests (45 pre-existing + 7 new), typecheck and biome clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sg8vJv9r2yqmnbtVPqM2vG
Promotes the omni-serve Model A work (persona + session/resume + streaming + ⏳→✅ reaction ack) from dev to main. Landed on dev via #2515 (replanted from the closed #2514 so PRs to main come from dev). Verified live end-to-end against the k8s omni deploy. NOTE: dev is behind main by 29 commits (pre-existing divergence) — this is a merge (not ff), so it only ADDS the Model A commits to main; a dev↔main resync is a separate hygiene follow-up.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation