Skip to content

Commit 31be4c3

Browse files
committed
Merge branch 'main' of github.com:lee101/codex-infinity
2 parents f8971b8 + ee69e2a commit 31be4c3

27 files changed

Lines changed: 435 additions & 29 deletions

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313

1414
## What makes Codex Infinity different?
1515

16-
Two arguments turn Codex into a fully autonomous coding agent:
16+
Three arguments turn Codex into a fully autonomous coding agent:
1717

1818
- **`--auto-next-steps`** -- After each response, automatically continues with the next logical steps (including testing)
1919
- **`--auto-next-idea`** -- Generates and implements new improvement ideas for your codebase
20+
- **`--auto-next-goal`** -- When a `/goal` completes, generates and starts the next goal automatically
2021

2122
```shell
2223
# Autonomous coding -- completes tasks then moves to the next one
@@ -25,6 +26,9 @@ codex-infinity --auto-next-steps "fix all lint errors and add tests"
2526
# Fully autonomous -- dreams up and implements improvements forever
2627
codex-infinity --auto-next-steps --auto-next-idea
2728

29+
# Goal loop -- finish a goal, then automatically create the next one
30+
codex-infinity --auto-next-goal "/goal improve startup parity with installed Codex"
31+
2832
# Full auto mode with autonomous continuation
2933
codex-infinity --full-auto --auto-next-steps
3034
```
@@ -56,6 +60,7 @@ codex-infinity "your prompt"
5660
|------|-------------|
5761
| `--auto-next-steps` | Auto-continue with next logical steps after each response |
5862
| `--auto-next-idea` | Auto-brainstorm and implement new improvement ideas |
63+
| `--auto-next-goal` | Auto-generate and start a new `/goal` after the current goal completes |
5964
| `--full-auto` | Low-friction sandboxed automatic execution |
6065
| `--yolo` | Skip approvals and sandbox (dangerous) |
6166
| `--yolo2` | Like yolo + disable command timeouts |
@@ -77,6 +82,9 @@ codex-infinity --full-auto --auto-next-steps "fix the failing test in auth.test.
7782
# Refactor with idea generation
7883
codex-infinity --auto-next-steps --auto-next-idea "refactor the API layer"
7984

85+
# Keep goal mode running autonomously
86+
codex-infinity --auto-next-goal "/goal improve benchmark coverage"
87+
8088
# Quick one-shot with yolo mode
8189
codex-infinity --yolo "add error handling to all API endpoints"
8290

@@ -91,6 +99,7 @@ codex-infinity --oss -m llama3 "explain this codebase"
9199

92100
- **Autonomous operation** -- `--auto-next-steps` keeps it working without intervention
93101
- **Idea generation** -- `--auto-next-idea` brainstorms and implements improvements
102+
- **Goal chaining** -- `--auto-next-goal` turns completed `/goal` work into the next objective
94103
- **AnyLLM** -- OpenAI, local models via LM Studio/Ollama, bring your own provider
95104
- **Local execution** -- runs entirely on your machine
96105
- **Concise prompts** -- stripped-down system prompts for faster, more focused responses

codex-cli/bin/codex-infinity.js

100644100755
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const __filename = fileURLToPath(import.meta.url);
1111
const __dirname = path.dirname(__filename);
1212

1313
const { platform, arch } = process;
14+
const isGnuLinuxRuntime = prefersGnu();
15+
const cliArgs = process.argv.slice(2);
1416

1517
function prefersGnu() {
1618
if (platform !== "linux") {
@@ -28,15 +30,14 @@ let targetTriples = [];
2830
switch (platform) {
2931
case "linux":
3032
case "android": {
31-
const preferGnu = prefersGnu();
3233
switch (arch) {
3334
case "x64":
34-
targetTriples = preferGnu
35+
targetTriples = isGnuLinuxRuntime
3536
? ["x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"]
3637
: ["x86_64-unknown-linux-musl", "x86_64-unknown-linux-gnu"];
3738
break;
3839
case "arm64":
39-
targetTriples = preferGnu
40+
targetTriples = isGnuLinuxRuntime
4041
? ["aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl"]
4142
: ["aarch64-unknown-linux-musl", "aarch64-unknown-linux-gnu"];
4243
break;
@@ -110,7 +111,7 @@ if (!binaryPath) {
110111
// ENOENT/dynamic-linker error.
111112
if (
112113
(platform === "linux" || platform === "android") &&
113-
!prefersGnu() &&
114+
!isGnuLinuxRuntime &&
114115
selectedTriple?.includes("-gnu")
115116
) {
116117
throw new Error(
@@ -215,13 +216,13 @@ const packageManagerEnvVar =
215216
env[packageManagerEnvVar] = "1";
216217
if (
217218
!env[DEFAULT_MODEL_ENV_VAR] &&
218-
!hasExplicitModelOverride(process.argv.slice(2)) &&
219-
!usesLocalModelProvider(process.argv.slice(2))
219+
!hasExplicitModelOverride(cliArgs) &&
220+
!usesLocalModelProvider(cliArgs)
220221
) {
221222
env[DEFAULT_MODEL_ENV_VAR] = DEFAULT_MODEL;
222223
}
223224

224-
const child = spawn(binaryPath, process.argv.slice(2), {
225+
const child = spawn(binaryPath, cliArgs, {
225226
stdio: "inherit",
226227
env,
227228
});

codex-cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codex-infinity/codex-infinity",
3-
"version": "1.3.41",
3+
"version": "1.3.42",
44
"license": "Apache-2.0",
55
"description": "Codex Infinity - a smarter coding agent that can run forever",
66
"bin": {

codex-cli/scripts/deploy_new_binary.sh

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,18 @@ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6161
codex_cli_root="$(cd "$script_dir/.." && pwd)"
6262
repo_root="$(cd "$codex_cli_root/.." && pwd)"
6363
codex_rs_root="$repo_root/codex-rs"
64-
binary_src="$codex_rs_root/target/release/codex"
6564
binary_dest="$codex_cli_root/vendor/x86_64-unknown-linux-gnu/codex/codex"
6665

66+
if ! command -v "${CC:-cc}" >/dev/null 2>&1; then
67+
if command -v gcc >/dev/null 2>&1; then
68+
export CC=gcc
69+
export CXX=g++
70+
else
71+
echo "error: gcc/cc is required to build tree-sitter; install build-essential." >&2
72+
exit 1
73+
fi
74+
fi
75+
6776
if ((!dry_run)); then
6877
if ! (cd "$codex_cli_root" && npm whoami >/dev/null); then
6978
echo "error: npm is not authenticated; run npm login before deploying." >&2
@@ -73,6 +82,8 @@ fi
7382

7483
cd "$codex_rs_root"
7584
cargo build --release -p codex-cli
85+
target_dir="$(cargo metadata --format-version 1 --no-deps | python3 -c "import json, sys; print(json.load(sys.stdin)['target_directory'])")"
86+
binary_src="$target_dir/release/codex"
7687

7788
mkdir -p "$(dirname "$binary_dest")"
7889
install -m 755 "$binary_src" "$binary_dest"

codex-rs/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# Codex CLI (Rust Implementation)
22

3-
> **New:** `--auto-next-steps` and `--auto-next-idea` let Codex keep going without manual prompts.
3+
> **New:** `--auto-next-steps`, `--auto-next-idea`, and `--auto-next-goal` let Codex keep going without manual prompts.
44
> Use `--auto-next-steps` to have Codex automatically continue with whatever natural follow-up work (including running relevant tests) makes sense after each response.
55
> Use `--auto-next-idea` to have Codex continually brainstorm fresh, creative improvements for the current project.
6-
> Enable both flags together for a “full auto” loop: Codex will alternate between advancing the existing plan and proposing new avenues to explore, so the session never idles unless you intervene.
6+
> Use `--auto-next-goal` to have Codex create and start the next `/goal` automatically when the current goal is completed.
7+
> Combine these flags for a “full auto” loop: Codex can advance the existing plan, propose new avenues to explore, and chain completed goals into fresh objectives so the session never idles unless you intervene.
78
89
We provide Codex CLI as a standalone, native executable to ensure a zero-dependency install.
910

codex-rs/arg0/src/lib.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,28 @@ fn build_runtime() -> anyhow::Result<tokio::runtime::Runtime> {
241241

242242
const ILLEGAL_ENV_VAR_PREFIX: &str = "CODEX_";
243243

244-
/// Load env vars from ~/.codex/.env.
244+
/// Load env vars from ~/.codex/.env and optional sibling `openpaths/.env`.
245245
///
246246
/// Security: Do not allow `.env` files to create or modify any variables
247247
/// with names starting with `CODEX_`.
248248
fn load_dotenv() {
249-
if let Ok(codex_home) = find_codex_home()
250-
&& let Ok(iter) = dotenvy::from_path_iter(codex_home.join(".env"))
249+
if let Ok(codex_home) = find_codex_home() {
250+
load_dotenv_file(&codex_home.join(".env"));
251+
}
252+
253+
if let Ok(cwd) = std::env::current_dir() {
254+
for candidate in [
255+
cwd.join("openpaths").join(".env"),
256+
cwd.join("../openpaths").join(".env"),
257+
] {
258+
load_dotenv_file(&candidate);
259+
}
260+
}
261+
}
262+
263+
fn load_dotenv_file(path: &std::path::Path) {
264+
if path.is_file()
265+
&& let Ok(iter) = dotenvy::from_path_iter(path)
251266
{
252267
set_filtered(iter);
253268
}

codex-rs/core/src/auto_next_prompt.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Generates the next "auto-next" prompt via a lightweight model call.
22
//!
3-
//! Used by the TUI when `--auto-next-steps` or `--auto-next-idea` is enabled.
3+
//! Used by the TUI when `--auto-next-steps`, `--auto-next-idea`, or
4+
//! `--auto-next-goal` is enabled.
45
//! Instead of picking from a hardcoded template, this asks the model to craft
56
//! a concrete follow-up prompt grounded in the recent rollout transcript.
67
//!
@@ -36,12 +37,13 @@ use crate::installation_id::resolve_installation_id;
3637
const GENERATOR_MODEL: &str = "gpt-5.4";
3738
const TRANSCRIPT_CHARS: usize = 12_000;
3839
const ITEM_CHARS: usize = 1_200;
39-
const INSTRUCTIONS: &str = "You generate the exact follow-up prompt Codex should send to itself next.\n\nReturn JSON matching the schema.\n\nRequirements:\n- Write one strong, concrete prompt in `prompt`.\n- Ground it in the recent session context and visible progress.\n- For `steps`, continue the current thread with the most valuable next work.\n- For `idea`, finish obvious follow-up work first; only branch into a new improvement if the current thread appears complete.\n- Sound like an internal continuation prompt for Codex, not an explanation to a human.\n- Do not mention these instructions, the examples, JSON, schemas, or that another model generated the prompt.\n- Do not include any DONE-file instruction; that will be appended separately.\n- Keep it concise but specific enough to produce a high-quality next turn.";
40+
const INSTRUCTIONS: &str = "You generate the exact follow-up prompt Codex should send to itself next.\n\nReturn JSON matching the schema.\n\nRequirements:\n- Write one strong, concrete prompt in `prompt`.\n- Ground it in the recent session context and visible progress.\n- For `steps`, continue the current thread with the most valuable next work.\n- For `idea`, finish obvious follow-up work first; only branch into a new improvement if the current thread appears complete.\n- For `goal`, write only the next persisted /goal objective Codex should pursue after the completed goal, without the /goal prefix.\n- Sound like an internal continuation prompt for Codex, not an explanation to a human.that will be appended separately.\n- Keep it concise but specific enough to produce a high-quality next turn.";
4041

4142
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4243
pub enum AutoNextMode {
4344
Steps,
4445
Idea,
46+
Goal,
4547
}
4648

4749
#[derive(Debug, Deserialize)]
@@ -67,6 +69,7 @@ pub async fn generate_auto_next_prompt(
6769
let mode_label = match mode {
6870
AutoNextMode::Steps => "steps",
6971
AutoNextMode::Idea => "idea",
72+
AutoNextMode::Goal => "goal",
7073
};
7174
let cwd_display = config.cwd.display().to_string();
7275
let user_message = format!(

codex-rs/core/src/model_family.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,16 @@ pub fn find_family_for_model(slug: &str) -> Option<ModelFamily> {
299299
slug, "openpaths-auto",
300300
needs_special_apply_patch_instructions: true,
301301
)
302+
} else if slug == "composer-2.5"
303+
|| slug == "composer-2.5-fast"
304+
|| slug.starts_with("composer-2.5")
305+
|| slug.starts_with("openpaths/composer-2.5")
306+
|| slug.starts_with("cursor/composer-2.5")
307+
{
308+
model_family!(
309+
slug, "composer-2.5",
310+
needs_special_apply_patch_instructions: true,
311+
)
302312
} else if slug.starts_with("glm-5") || slug.starts_with("GLM-5") {
303313
model_family!(
304314
slug, "glm-5",

codex-rs/model-provider-info/src/lib.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ const OPENROUTER_PROVIDER_NAME: &str = "OpenRouter";
4040
pub const OPENROUTER_PROVIDER_ID: &str = "openrouter";
4141
const OPENPATHS_PROVIDER_NAME: &str = "OpenPaths";
4242
pub const OPENPATHS_PROVIDER_ID: &str = "openpaths";
43+
pub const CURSOR_PROVIDER_NAME: &str = "Cursor";
44+
pub const CURSOR_PROVIDER_ID: &str = "cursor";
4345
const ZHIPU_PROVIDER_NAME: &str = "Z.AI (Zhipu)";
4446
pub const ZHIPU_PROVIDER_ID: &str = "zhipu";
4547
const DEEPSEEK_PROVIDER_NAME: &str = "DeepSeek";
@@ -405,7 +407,7 @@ impl ModelProviderInfo {
405407
pub fn create_openpaths_provider() -> ModelProviderInfo {
406408
ModelProviderInfo {
407409
name: OPENPATHS_PROVIDER_NAME.into(),
408-
base_url: Some("https://openpaths.io/v1".into()),
410+
base_url: Some(openpaths_base_url()),
409411
env_key: Some("OPENPATHS_API_KEY".into()),
410412
env_key_instructions: Some(
411413
"Get your API key from https://openpaths.io and set OPENPATHS_API_KEY".into(),
@@ -426,6 +428,31 @@ impl ModelProviderInfo {
426428
}
427429
}
428430

431+
pub fn create_cursor_provider() -> ModelProviderInfo {
432+
ModelProviderInfo {
433+
name: CURSOR_PROVIDER_NAME.into(),
434+
base_url: Some(cursor_base_url()),
435+
env_key: Some("CURSOR_API_KEY".into()),
436+
env_key_instructions: Some(
437+
"Get your API key from https://cursor.com/dashboard/integrations and set CURSOR_API_KEY"
438+
.into(),
439+
),
440+
experimental_bearer_token: None,
441+
auth: None,
442+
wire_api: WireApi::Responses,
443+
query_params: None,
444+
http_headers: None,
445+
env_http_headers: None,
446+
request_max_retries: None,
447+
stream_max_retries: None,
448+
stream_idle_timeout_ms: None,
449+
websocket_connect_timeout_ms: None,
450+
requires_openai_auth: false,
451+
supports_websockets: false,
452+
..Default::default()
453+
}
454+
}
455+
429456
pub fn create_zhipu_provider() -> ModelProviderInfo {
430457
ModelProviderInfo {
431458
name: ZHIPU_PROVIDER_NAME.into(),
@@ -511,6 +538,8 @@ impl ModelProviderInfo {
511538
slug.strip_prefix("google/").unwrap_or(slug)
512539
} else if self.name == OPENPATHS_PROVIDER_NAME {
513540
slug.strip_prefix("openpaths/").unwrap_or(slug)
541+
} else if self.name == CURSOR_PROVIDER_NAME {
542+
slug.strip_prefix("cursor/").unwrap_or(slug)
514543
} else if self.name == ZHIPU_PROVIDER_NAME {
515544
slug.strip_prefix("zhipu/")
516545
.or_else(|| slug.strip_prefix("z-ai/"))
@@ -556,6 +585,7 @@ pub fn built_in_model_providers(
556585
(OPENAI_PROVIDER_ID, openai_provider),
557586
(OPENROUTER_PROVIDER_ID, P::create_openrouter_provider()),
558587
(OPENPATHS_PROVIDER_ID, P::create_openpaths_provider()),
588+
(CURSOR_PROVIDER_ID, P::create_cursor_provider()),
559589
(GEMINI_PROVIDER_ID, P::create_gemini_provider()),
560590
(ZHIPU_PROVIDER_ID, P::create_zhipu_provider()),
561591
(DEEPSEEK_PROVIDER_ID, P::create_deepseek_provider()),
@@ -580,6 +610,39 @@ fn non_empty_env_var(name: &str) -> bool {
580610
.is_some_and(|value| !value.trim().is_empty())
581611
}
582612

613+
fn openpaths_base_url() -> String {
614+
std::env::var("OPENPATHS_BASE_URL")
615+
.ok()
616+
.filter(|value| !value.trim().is_empty())
617+
.map(|value| {
618+
let trimmed = value.trim().trim_end_matches('/');
619+
if trimmed.ends_with("/v1") {
620+
trimmed.to_string()
621+
} else {
622+
format!("{trimmed}/v1")
623+
}
624+
})
625+
.unwrap_or_else(|| "https://openpaths.io/v1".to_string())
626+
}
627+
628+
fn cursor_base_url() -> String {
629+
std::env::var("CURSOR_BASE_URL")
630+
.ok()
631+
.filter(|value| !value.trim().is_empty())
632+
.map(|value| value.trim().trim_end_matches('/').to_string())
633+
.unwrap_or_else(|| "https://api.cursor.com".to_string())
634+
}
635+
636+
fn is_composer_model_slug(lower: &str) -> bool {
637+
let slug = lower
638+
.strip_prefix("openpaths/")
639+
.or_else(|| lower.strip_prefix("cursor/"))
640+
.unwrap_or(lower);
641+
slug == "composer-2.5"
642+
|| slug == "composer-2.5-fast"
643+
|| slug.starts_with("composer-2.5-")
644+
}
645+
583646
pub fn infer_builtin_provider_id_for_model(model: &str) -> Option<&'static str> {
584647
let lower = model.to_lowercase();
585648
if (lower.starts_with("glm-")
@@ -611,6 +674,14 @@ pub fn infer_builtin_provider_id_for_model(model: &str) -> Option<&'static str>
611674
{
612675
return Some(OPENPATHS_PROVIDER_ID);
613676
}
677+
if is_composer_model_slug(&lower) {
678+
if non_empty_env_var("OPENPATHS_API_KEY") {
679+
return Some(OPENPATHS_PROVIDER_ID);
680+
}
681+
if non_empty_env_var("CURSOR_API_KEY") {
682+
return Some(CURSOR_PROVIDER_ID);
683+
}
684+
}
614685
match model.split_once('/') {
615686
Some(("google", _)) if non_empty_env_var("GEMINI_API_KEY") => Some(GEMINI_PROVIDER_ID),
616687
Some(("google", _)) if non_empty_env_var("OPENROUTER_API_KEY") => {
@@ -623,6 +694,7 @@ pub fn infer_builtin_provider_id_for_model(model: &str) -> Option<&'static str>
623694
Some(("openpaths", _)) if non_empty_env_var("OPENPATHS_API_KEY") => {
624695
Some(OPENPATHS_PROVIDER_ID)
625696
}
697+
Some(("cursor", _)) if non_empty_env_var("CURSOR_API_KEY") => Some(CURSOR_PROVIDER_ID),
626698
Some(("deepseek", _)) if non_empty_env_var("DEEPSEEK_API_KEY") => {
627699
Some(DEEPSEEK_PROVIDER_ID)
628700
}

0 commit comments

Comments
 (0)