Add Grok orchestration group routing (#8058)

* docs: design Grok orchestration group

* docs: plan Grok orchestration group implementation

* fix: add Grok orchestration group

* test(orchestration): accept Windows skill newlines

* Fix @grok orchestration group matching and remove stale planning docs

- Reuse the shared buildAgentNameRe matcher in groups.ts instead of a
  divergent local regex, so orchestration groups honor the same
  Windows launcher-suffix rule (grok.exe/.cmd/.bat/.ps1) as the rest
  of Orca's agent-title detection.
- Add test coverage for real Grok OSC title shapes (spinner-collapsed,
  session titles) and Windows launcher-suffix titles.
- Delete the now-completed design and implementation-plan docs for
  the Grok orchestration group work.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
This commit is contained in:
BingZ
2026-07-11 21:43:20 -07:00
committed by GitHub
co-authored by Jinjing
parent 92ea918b63
commit be258e23ec
17 changed files with 109 additions and 63 deletions
@@ -120,10 +120,7 @@ export class KimiSessionIndexCache {
}
}
function identitiesMatch(
left: KimiSessionIndexIdentity,
right: KimiSessionIndexIdentity
): boolean {
function identitiesMatch(left: KimiSessionIndexIdentity, right: KimiSessionIndexIdentity): boolean {
return (
left.changeTimeMs === right.changeTimeMs &&
left.mtimeMs === right.mtimeMs &&
@@ -114,7 +114,7 @@ describe('submodule path cache', () => {
expect(gitExecFileAsyncMock).toHaveBeenCalledTimes(2)
})
it('does not reuse another branch\'s submodule paths after local or WSL checkout', async () => {
it("does not reuse another branch's submodule paths after local or WSL checkout", async () => {
let modulePath = 'main-lib'
gitExecFileAsyncMock.mockImplementation((args: string[]) => {
if (args[0] === 'checkout') {
@@ -219,9 +219,7 @@ describe('submodule path cache', () => {
...runtime,
checkoutExistingBranch: true
})
await expect(listSubmodulePaths('/repo-feature', runtime)).resolves.toEqual([
'recreated-lib'
])
await expect(listSubmodulePaths('/repo-feature', runtime)).resolves.toEqual(['recreated-lib'])
const configReads = gitExecFileAsyncMock.mock.calls.filter(
([args]) => args[0] === 'config' && args.includes('.gitmodules')
+2 -7
View File
@@ -1,8 +1,5 @@
import { describe, expect, it } from 'vitest'
import {
DEFAULT_WARNING_DEDUPE_MAX_KEYS,
shouldEmitBoundedWarning
} from './bounded-warning-dedupe'
import { DEFAULT_WARNING_DEDUPE_MAX_KEYS, shouldEmitBoundedWarning } from './bounded-warning-dedupe'
describe('shouldEmitBoundedWarning', () => {
it('keeps retained warning keys quiet without cascade eviction after saturation', () => {
@@ -29,8 +26,6 @@ describe('shouldEmitBoundedWarning', () => {
)
expect(keys.filter((key) => shouldEmitBoundedWarning(warningKeys, key))).toEqual(keys)
expect(keys.filter((key) => shouldEmitBoundedWarning(warningKeys, key))).toEqual([
keys.at(-1)
])
expect(keys.filter((key) => shouldEmitBoundedWarning(warningKeys, key))).toEqual([keys.at(-1)])
})
})
+1 -4
View File
@@ -155,10 +155,7 @@ vi.mock('./ssh', () => ({
}))
import { registerRepoHandlers } from './repos'
import {
clearSubmodulePathsCacheForTests,
listSubmodulePaths
} from '../git/status'
import { clearSubmodulePathsCacheForTests, listSubmodulePaths } from '../git/status'
beforeEach(() => {
clearGitCapabilityStateForTests()
@@ -30,6 +30,7 @@ describe('isGroupAddress', () => {
expect(isGroupAddress('@idle')).toBe(true)
expect(isGroupAddress('@claude')).toBe(true)
expect(isGroupAddress('@droid')).toBe(true)
expect(isGroupAddress('@grok')).toBe(true)
expect(isGroupAddress('@worktree:wt_1')).toBe(true)
})
@@ -169,6 +170,54 @@ describe('resolveGroupAddress', () => {
const result = resolveGroupAddress('@Claude', 'term_a', terminals, noStatus)
expect(result).toEqual(['term_b'])
})
it('matches @grok as a standalone title token and excludes sender', () => {
const terminals = [
makeSummary('term_a', { title: 'Grok' }),
makeSummary('term_b', { title: 'GROK CLI' }),
makeSummary('term_c', { title: '⠋ Grok' }),
makeSummary('term_d', { title: 'ngrok' }),
makeSummary('term_e', { title: '/tmp/grok' }),
makeSummary('term_f', { title: 'my-grok-worker' }),
makeSummary('term_g', { title: 'Codex CLI' })
]
const result = resolveGroupAddress('@GrOk', 'term_a', terminals, noStatus)
expect(result).toEqual(['term_b', 'term_c'])
})
// Why: the resolver sees the raw OSC title, and Grok CLI's real working/session
// titles carry a trailing " - grok" identity or a spinner-collapsed "⠋ grok"
// (see terminal-title-agent-type.ts). Prove those production shapes resolve.
it('matches real Grok OSC working and session titles', () => {
const terminals = [
makeSummary('coordinator', { title: 'Coordinator' }),
makeSummary('term_rotating', { title: '⠋ - fix the flaky suite - grok' }),
makeSummary('term_collapsed', { title: '⠋ grok' }),
makeSummary('term_session', { title: 'Fix the auth bug - grok' })
]
const result = resolveGroupAddress('@grok', 'coordinator', terminals, noStatus)
expect(result).toEqual(['term_rotating', 'term_collapsed', 'term_session'])
})
// Why: Windows agent titles can surface the launcher process name (`grok.exe`);
// the shared matcher accepts .exe/.cmd/.bat/.ps1 suffixes but still rejects
// arbitrary dotted fragments like `grok.py`.
it('matches Windows launcher-suffix titles but not arbitrary dotted tokens', () => {
const terminals = [
makeSummary('coordinator', { title: 'Coordinator' }),
makeSummary('term_exe', { title: 'grok.exe' }),
makeSummary('term_cmd', { title: 'grok.cmd running' }),
makeSummary('term_dotted', { title: 'grok.py' })
]
const result = resolveGroupAddress('@grok', 'coordinator', terminals, noStatus)
expect(result).toEqual(['term_exe', 'term_cmd'])
})
})
describe('unknown groups', () => {
+7 -7
View File
@@ -1,3 +1,4 @@
import { buildAgentNameRe } from '../../../shared/agent-name-token-match'
import type { RuntimeTerminalSummary } from '../../../shared/runtime-types'
// Why: group addresses enable broadcast messaging to logical groups of agents.
@@ -11,7 +12,8 @@ const AGENT_NAME_GROUPS = [
'opencode',
'mimo',
'gemini',
'droid'
'droid',
'grok'
] as const
export type GroupAddress =
@@ -24,13 +26,11 @@ export function isGroupAddress(to: string): boolean {
return to.startsWith('@')
}
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
function titleMatchesAgentNameGroup(title: string, agentName: string): boolean {
const tokenRe = new RegExp(`(?<![\\w./\\\\-])${escapeRegExp(agentName)}(?![\\w./\\\\-])`, 'i')
return tokenRe.test(title)
// Why: reuse the shared whole-token matcher so orchestration groups honor the
// same Windows launcher-suffix rule (e.g. `grok.exe`) as the rest of Orca's
// agent-title detection, instead of maintaining a divergent regex here.
return buildAgentNameRe(agentName).test(title)
}
export function resolveGroupAddress(