Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/__tests__/runtime-guidance-plan-ralph.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { CliAgentType } from '../team/model-contract.js';

const availability = vi.hoisted(() => ({
claude: true,
codex: false,
gemini: false,
cursor: false,
}));

vi.mock('../team/model-contract.js', () => ({
isCliAvailable: (agentType: 'claude' | 'codex' | 'gemini') => availability[agentType],
isCliAvailable: (agentType: CliAgentType) => availability[agentType],
}));

import {
Expand All @@ -20,6 +22,7 @@ describe('runtime-guidance: ralplan/plan/ralph Codex availability', () => {
availability.claude = true;
availability.codex = false;
availability.gemini = false;
availability.cursor = false;
});

describe('renderSkillRuntimeGuidance for plan-family skills', () => {
Expand Down Expand Up @@ -75,7 +78,7 @@ describe('runtime-guidance: ralplan/plan/ralph Codex availability', () => {

describe('detectSkillRuntimeAvailability safety', () => {
it('returns false for a provider whose detector throws instead of crashing', () => {
const throwingDetector = (agentType: 'claude' | 'codex' | 'gemini') => {
const throwingDetector = (agentType: CliAgentType) => {
if (agentType === 'codex') {
throw new Error(
'External LLM provider "codex" is blocked by security policy (disableExternalLLM).',
Expand Down
4 changes: 2 additions & 2 deletions src/cli/team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { isProcessAlive } from '../platform/index.js';
import { getGlobalOmcStatePath } from '../utils/paths.js';

const JOB_ID_PATTERN = /^omc-[a-z0-9]{1,16}$/;
const VALID_CLI_AGENT_TYPES = new Set(['claude', 'codex', 'gemini']);
const VALID_CLI_AGENT_TYPES = new Set(['claude', 'codex', 'gemini', 'cursor']);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wire cursor support into the active team CLI parser

Adding 'cursor' to VALID_CLI_AGENT_TYPES here only updates the legacy src/cli/team.ts path, but the actual omc team command is dispatched via src/cli/index.ts to src/cli/commands/team.ts, where VALID_TEAM_CLI_AGENT_TYPES still excludes cursor and normalizeWorkerSpecSegment() treats unknown tokens as roles. In practice, omc team 1:cursor "..." is parsed as a Claude worker with role cursor, so the new provider is silently not selected on the primary CLI surface.

Useful? React with 👍 / 👎.

const SUBCOMMANDS = new Set(['start', 'status', 'wait', 'cleanup', 'resume', 'shutdown', 'api', 'help', '--help', '-h']);

const SUPPORTED_API_OPERATIONS = new Set([
Expand Down Expand Up @@ -685,7 +685,7 @@ export async function teamCleanupCommand(

export const TEAM_USAGE = `
Usage:
omc team start --agent <claude|codex|gemini>[,<agent>...] --task "<task>" [--count N] [--name TEAM] [--cwd DIR] [--new-window] [--json]
omc team start --agent <claude|codex|gemini|cursor>[,<agent>...] --task "<task>" [--count N] [--name TEAM] [--cwd DIR] [--new-window] [--json]
omc team status <job_id|team_name> [--json] [--cwd DIR]
omc team wait <job_id> [--timeout-ms MS] [--json]
omc team cleanup <job_id> [--grace-ms MS] [--json]
Expand Down
1 change: 1 addition & 0 deletions src/team/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const DEFAULT_CAPABILITIES: Record<WorkerBackend, WorkerCapability[]> = {
'tmux-claude': ['code-edit', 'testing', 'general'],
'tmux-codex': ['code-review', 'security-review', 'architecture', 'refactoring'],
'tmux-gemini': ['ui-design', 'documentation', 'research', 'code-edit'],
'tmux-cursor': ['code-edit', 'refactoring', 'general'],
};

/**
Expand Down
1 change: 1 addition & 0 deletions src/team/cli-detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ export function detectAllClis(): Record<string, CliInfo> {
claude: detectCli('claude'),
codex: detectCli('codex'),
gemini: detectCli('gemini'),
cursor: detectCli('cursor-agent'),
};
}
9 changes: 8 additions & 1 deletion src/team/cli-worker-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ export function shouldInjectContract(
provider: CliAgentType | null | undefined,
): boolean {
if (!role || !provider) return false;
if (provider === 'claude') return false;
// Claude workers speak through the team messaging API directly.
// Cursor workers run as interactive REPLs — they cannot perform the
// write-verdict-and-exit dance the contract requires, so reviewer
// roles must not be assigned to cursor in the first place. The
// role-router and worker-bootstrap guidance both flag this; here we
// simply skip contract injection if a cursor worker somehow lands on
// a CONTRACT_ROLES role rather than emit instructions it cannot follow.
if (provider === 'claude' || provider === 'cursor') return false;
return CONTRACT_ROLES.has(role);
}

Expand Down
20 changes: 19 additions & 1 deletion src/team/model-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { normalizeToCcAlias } from '../features/delegation-enforcer.js';
import { isBedrock, isVertexAI, isProviderSpecificModelId } from '../config/models.js';
import { isExternalLLMDisabled } from '../lib/security-config.js';

export type CliAgentType = 'claude' | 'codex' | 'gemini';
export type CliAgentType = 'claude' | 'codex' | 'gemini' | 'cursor';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep scaling agent validation in sync with new provider

After expanding CliAgentType to include 'cursor', the scaling path is now inconsistent: scaleUp() in src/team/scaling.ts still validates with new Set(['claude','codex','gemini']) and rejects cursor as unknown. That means teams started with cursor workers cannot be scaled up with the same provider, which breaks team lifecycle behavior for the newly added agent type.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat cursor teams as CLI workers during shutdown

Adding cursor to CliAgentType makes cursor-only teams valid, but shutdownTeam still classifies CLI workers using new Set(['claude','codex','gemini']) (src/team/runtime.ts:926). For a team started with agentTypes: ['cursor'], isCliWorkerTeam becomes false, so shutdown waits the full ACK timeout for shutdown-ack.json files that CLI-pane workers do not emit, causing avoidable 30s hangs before tmux cleanup. Please include cursor in that allowlist (or derive it from CliAgentType) so shutdown remains fast for the new provider.

Useful? React with 👍 / 👎.


export interface CliAgentContract {
agentType: CliAgentType;
Expand Down Expand Up @@ -224,6 +224,24 @@ const CONTRACTS: Record<CliAgentType, CliAgentContract> = {
return rawOutput.trim();
},
},
cursor: {
agentType: 'cursor',
binary: 'cursor-agent',
installInstructions: 'Install Cursor Agent CLI: see https://docs.cursor.com/cli',
// cursor-agent runs as an interactive REPL — no exit-on-complete prompt mode.
// Keep supportsPromptMode false so the verdict-file contract path
// (CONTRACT_ROLES + shouldInjectContract) skips this provider; cursor
// workers participate as executors only.
supportsPromptMode: false,
buildLaunchArgs(_model?: string, extraFlags: string[] = []): string[] {
// Minimal flags — cursor-agent owns its own session/auth state.
// The model is selected interactively inside cursor-agent itself.
return [...extraFlags];
},
parseOutput(rawOutput: string): string {
return rawOutput.trim();
},
},
};

export function getContract(agentType: CliAgentType): CliAgentContract {
Expand Down
4 changes: 2 additions & 2 deletions src/team/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export interface TaskFailureSidecar {
}

/** Worker backend type */
export type WorkerBackend = 'claude-native' | 'mcp-codex' | 'mcp-gemini' | 'tmux-claude' | 'tmux-codex' | 'tmux-gemini';
export type WorkerBackend = 'claude-native' | 'mcp-codex' | 'mcp-gemini' | 'tmux-claude' | 'tmux-codex' | 'tmux-gemini' | 'tmux-cursor';

/** Worker capability tag */
export type WorkerCapability =
Expand Down Expand Up @@ -253,7 +253,7 @@ export interface WorkerInfo {
name: string;
index: number;
role: string;
worker_cli?: 'codex' | 'claude' | 'gemini';
worker_cli?: 'codex' | 'claude' | 'gemini' | 'cursor';
assigned_tasks: string[];
pid?: number;
pane_id?: string;
Expand Down
7 changes: 7 additions & 0 deletions src/team/worker-bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ function agentTypeGuidance(agentType: CliAgentType): string {
'- Keep commit-sized changes scoped to assigned files only; no broad refactors.',
`- CRITICAL: You MUST run \`${claimTaskCommand}\` before starting work and \`${transitionTaskStatusCommand}\` when done. Do not exit without transitioning the task status.`,
].join('\n');
case 'cursor':
return [
'### Agent-Type Guidance (cursor)',
'- You are an interactive REPL (cursor-agent), not a one-shot CLI. Stay in the session; the leader will continue to send prompts via mailbox.',
`- You MUST run \`${claimTaskCommand}\` before starting work and \`${transitionTaskStatusCommand}\` when done. Then keep waiting for the next mailbox message; do NOT type \`/exit\` unless the leader sends an explicit shutdown.`,
'- Reviewer/critic/security-review roles are NOT supported for cursor workers — those require a verdict-file write-and-exit which the REPL does not perform. Take only executor-style tasks.',
].join('\n');
case 'claude':
default:
return [
Expand Down
Loading