jeo-code 0.7.29 → 0.7.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  The README mirrors the latest 5 entries — regenerate with `bun run changelog:sync`.
8
8
 
9
+ ## [0.7.30] - 2026-07-02
10
+ _GPT/Codex OAuth calls work again, and the 30-minute turn budget now limits no-progress stalls instead of killing genuinely progressing autonomous runs._
11
+
12
+ ### Fixed
13
+ - **GPT/Codex OAuth no longer sends `max_output_tokens` to the ChatGPT/Codex backend.** `0.7.28` moved the computed output cap onto the shared Responses payload, but the OAuth Codex endpoint rejects that public-API-only parameter with `HTTP 400 {"detail":"Unsupported parameter: max_output_tokens"}`. The cap is now applied only on the public `/v1/responses` API-key path; OAuth requests keep using the Codex backend shape that `jeo doctor` probes.
14
+ - **`JEO_TURN_MAX_MS` is now a stall budget, not an absolute kill switch.** The old implementation measured 30 minutes from turn start and could terminate a long autonomous run even while it was still executing tools and making progress. The clock now resets on every executed tool step and on mid-turn steering, so it only stops no-progress loops such as refusal backoff spins, endless provider retries, or bounce loops. The existing step hard cap and repeat/cycle/failure guards still bound genuinely long runs.
15
+
16
+ ### Verified
17
+ - `bun test` green — 2147 pass / 0 fail across 244 files; `bun run typecheck` clean.
18
+ - Live GPT/Codex OAuth smoke tests through the installed `jeo` symlink: `jeo chat "Say exactly: hello" --model gpt-5.5` and `jeo chat "Reply with one word: pong" --model gpt-5.5` both returned the requested text. Added/updated regression coverage in `test/codex-responses.test.ts` and `test/cycle-and-turn-budget.test.ts`.
19
+
9
20
  ## [0.7.29] - 2026-07-02
10
21
  _gajae-code safety-refusal retry parity: Anthropic/OAuth content-classifier refusals no longer end the turn with a fatal "declined to answer" message after the context-reset ladder._
11
22
 
package/README.ja.md CHANGED
@@ -205,11 +205,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
205
205
  ## 変更履歴 (Changelog)
206
206
 
207
207
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
208
+ - **[0.7.30]** (2026-07-02) — GPT/Codex OAuth calls work again, and the 30-minute turn budget now limits no-progress stalls instead of killing genuinely progressing autonomous runs.
208
209
  - **[0.7.29]** (2026-07-02) — gajae-code safety-refusal retry parity: Anthropic/OAuth content-classifier refusals no longer end the turn with a fatal "declined to answer" message after the context-reset ladder.
209
210
  - **[0.7.28]** (2026-07-02) — gajae-code 0.7.9–0.7.10 OAuth + model-execution parity: generic long rate-limit windows are retried (never fatal), retry waits are abort-cancellable, Codex prompt caching via session correlation, and the Kimi Code device-flow OAuth is completed end-to-end (catalog, gating, discovery, tests).
210
211
  - **[0.7.27]** (2026-07-02) — Extended-keyboard decode layer: Esc/Ctrl+C work again on kitty-protocol terminals, Shift+Enter breaks lines on every terminal incl. Windows, row/draft-aware Cmd+←→/↑↓ + Home/End/Ctrl+Home/End, Ctrl+V text-paste fallback. Plus Anthropic 5th-gen omp-parity: catalog-derived output budgets end thinking-vs-reply starvation; refusals fail fast at the transport and the recovery ladder strips replayed thinking blocks.
211
212
  - **[0.7.26]** (2026-07-02) — Esc/Ctrl+C reliably clears the input box (fixes a gate the earlier reorder missed), and un-bracketed multi-line pastes no longer shred into premature per-line submits.
212
- - **[0.7.25]** (2026-07-02) — Shift+Enter now reliably distinguishable from Enter on kitty/WezTerm/iTerm2/xterm via modifyOtherKeys + kitty keyboard protocol; row-aware Home/End and Cmd+Left/Right in multi-row drafts.
213
213
 
214
214
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
215
215
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -205,11 +205,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
205
205
  ## 변경 이력 (Changelog)
206
206
 
207
207
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
208
+ - **[0.7.30]** (2026-07-02) — GPT/Codex OAuth calls work again, and the 30-minute turn budget now limits no-progress stalls instead of killing genuinely progressing autonomous runs.
208
209
  - **[0.7.29]** (2026-07-02) — gajae-code safety-refusal retry parity: Anthropic/OAuth content-classifier refusals no longer end the turn with a fatal "declined to answer" message after the context-reset ladder.
209
210
  - **[0.7.28]** (2026-07-02) — gajae-code 0.7.9–0.7.10 OAuth + model-execution parity: generic long rate-limit windows are retried (never fatal), retry waits are abort-cancellable, Codex prompt caching via session correlation, and the Kimi Code device-flow OAuth is completed end-to-end (catalog, gating, discovery, tests).
210
211
  - **[0.7.27]** (2026-07-02) — Extended-keyboard decode layer: Esc/Ctrl+C work again on kitty-protocol terminals, Shift+Enter breaks lines on every terminal incl. Windows, row/draft-aware Cmd+←→/↑↓ + Home/End/Ctrl+Home/End, Ctrl+V text-paste fallback. Plus Anthropic 5th-gen omp-parity: catalog-derived output budgets end thinking-vs-reply starvation; refusals fail fast at the transport and the recovery ladder strips replayed thinking blocks.
211
212
  - **[0.7.26]** (2026-07-02) — Esc/Ctrl+C reliably clears the input box (fixes a gate the earlier reorder missed), and un-bracketed multi-line pastes no longer shred into premature per-line submits.
212
- - **[0.7.25]** (2026-07-02) — Shift+Enter now reliably distinguishable from Enter on kitty/WezTerm/iTerm2/xterm via modifyOtherKeys + kitty keyboard protocol; row-aware Home/End and Cmd+Left/Right in multi-row drafts.
213
213
 
214
214
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
215
215
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -232,11 +232,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
232
232
  ## Changelog
233
233
 
234
234
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
235
+ - **[0.7.30]** (2026-07-02) — GPT/Codex OAuth calls work again, and the 30-minute turn budget now limits no-progress stalls instead of killing genuinely progressing autonomous runs.
235
236
  - **[0.7.29]** (2026-07-02) — gajae-code safety-refusal retry parity: Anthropic/OAuth content-classifier refusals no longer end the turn with a fatal "declined to answer" message after the context-reset ladder.
236
237
  - **[0.7.28]** (2026-07-02) — gajae-code 0.7.9–0.7.10 OAuth + model-execution parity: generic long rate-limit windows are retried (never fatal), retry waits are abort-cancellable, Codex prompt caching via session correlation, and the Kimi Code device-flow OAuth is completed end-to-end (catalog, gating, discovery, tests).
237
238
  - **[0.7.27]** (2026-07-02) — Extended-keyboard decode layer: Esc/Ctrl+C work again on kitty-protocol terminals, Shift+Enter breaks lines on every terminal incl. Windows, row/draft-aware Cmd+←→/↑↓ + Home/End/Ctrl+Home/End, Ctrl+V text-paste fallback. Plus Anthropic 5th-gen omp-parity: catalog-derived output budgets end thinking-vs-reply starvation; refusals fail fast at the transport and the recovery ladder strips replayed thinking blocks.
238
239
  - **[0.7.26]** (2026-07-02) — Esc/Ctrl+C reliably clears the input box (fixes a gate the earlier reorder missed), and un-bracketed multi-line pastes no longer shred into premature per-line submits.
239
- - **[0.7.25]** (2026-07-02) — Shift+Enter now reliably distinguishable from Enter on kitty/WezTerm/iTerm2/xterm via modifyOtherKeys + kitty keyboard protocol; row-aware Home/End and Cmd+Left/Right in multi-row drafts.
240
240
 
241
241
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
242
242
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -205,11 +205,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
205
205
  ## 更新日志 (Changelog)
206
206
 
207
207
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
208
+ - **[0.7.30]** (2026-07-02) — GPT/Codex OAuth calls work again, and the 30-minute turn budget now limits no-progress stalls instead of killing genuinely progressing autonomous runs.
208
209
  - **[0.7.29]** (2026-07-02) — gajae-code safety-refusal retry parity: Anthropic/OAuth content-classifier refusals no longer end the turn with a fatal "declined to answer" message after the context-reset ladder.
209
210
  - **[0.7.28]** (2026-07-02) — gajae-code 0.7.9–0.7.10 OAuth + model-execution parity: generic long rate-limit windows are retried (never fatal), retry waits are abort-cancellable, Codex prompt caching via session correlation, and the Kimi Code device-flow OAuth is completed end-to-end (catalog, gating, discovery, tests).
210
211
  - **[0.7.27]** (2026-07-02) — Extended-keyboard decode layer: Esc/Ctrl+C work again on kitty-protocol terminals, Shift+Enter breaks lines on every terminal incl. Windows, row/draft-aware Cmd+←→/↑↓ + Home/End/Ctrl+Home/End, Ctrl+V text-paste fallback. Plus Anthropic 5th-gen omp-parity: catalog-derived output budgets end thinking-vs-reply starvation; refusals fail fast at the transport and the recovery ladder strips replayed thinking blocks.
211
212
  - **[0.7.26]** (2026-07-02) — Esc/Ctrl+C reliably clears the input box (fixes a gate the earlier reorder missed), and un-bracketed multi-line pastes no longer shred into premature per-line submits.
212
- - **[0.7.25]** (2026-07-02) — Shift+Enter now reliably distinguishable from Enter on kitty/WezTerm/iTerm2/xterm via modifyOtherKeys + kitty keyboard protocol; row-aware Home/End and Cmd+Left/Right in multi-row drafts.
213
213
 
214
214
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
215
215
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.7.29",
3
+ "version": "0.7.30",
4
4
 
5
5
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
6
6
  "type": "module",
@@ -286,10 +286,12 @@ export interface AgentLoopResult {
286
286
  }
287
287
 
288
288
 
289
- /** Wall-clock budget for ONE agent turn (ms). JEO_TURN_MAX_MS overrides; 0 disables.
290
- * Default 30 minutes: long autonomous runs stay alive, while a turn that spins in
291
- * "thinking" (huge contexts, endless extensions) is guaranteed to terminate into
292
- * the consolidation wrap-up instead of running for hours. */
289
+ /** Wall-clock STALL budget for ONE agent turn (ms): the maximum time a turn may run
290
+ * WITHOUT PROGRESS (no executed tool step). JEO_TURN_MAX_MS overrides; 0 disables.
291
+ * Default 30 minutes: long autonomous runs that keep executing tools stay alive
292
+ * indefinitely (the step budget's hard cap bounds them), while a turn that spins in
293
+ * "thinking" (refusal backoff, endless provider retries) is guaranteed to terminate
294
+ * into the consolidation wrap-up instead of running for hours. */
293
295
  export function turnMaxMs(env: Record<string, string | undefined> = process.env): number {
294
296
  const raw = jeoEnv("TURN_MAX_MS", env);
295
297
  if (raw !== undefined && raw !== "") {
@@ -364,12 +366,18 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
364
366
  const ev = opts.events ?? {};
365
367
  const maxHistoryTokens = Math.max(10_000, opts.maxHistoryTokens ?? 80_000);
366
368
 
367
- // Wall-clock turn budget — the definitive "never sits in thinking forever"
368
- // guarantee. Step budgets bound the COUNT of model calls; this bounds their total
369
- // TIME: a turn that crosses it stops at the next loop boundary and consolidates a
370
- // wrap-up instead of spinning for hours under a generous dynamic step cap.
369
+ // Wall-clock STALL budget — the definitive "never sits in thinking forever"
370
+ // guarantee. Step budgets bound the COUNT of model calls; this bounds the time a
371
+ // turn may spend WITHOUT PROGRESS (no executed tool step): refusal-backoff spins,
372
+ // endless provider retries, and bounce loops terminate into the consolidation
373
+ // wrap-up, while a long-running turn that keeps executing tools stays alive —
374
+ // its termination is owned by the step budget's hard cap and the spin guards.
375
+ // Field regression this replaces: an ABSOLUTE 30m budget measured from turn start
376
+ // killed genuinely progressing autonomous runs mid-work.
371
377
  const turnStartedAt = Date.now();
372
378
  const turnBudgetMs = turnMaxMs();
379
+ // Reset on every executed tool step and on mid-turn steering.
380
+ let lastProgressAt = turnStartedAt;
373
381
  // "steps" | "time" — drives honest wording in the consolidation message.
374
382
  let stopKind: "steps" | "time" = "steps";
375
383
  let step = 1;
@@ -449,7 +457,8 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
449
457
  // ladder is exhausted the loop keeps resending with capped exponential backoff
450
458
  // instead of surfacing a terminal error (gjc parity: a refusal is retried until
451
459
  // it clears or the user cancels — Esc aborts the backoff wait via opts.signal,
452
- // and the turn wall-clock budget still bounds the whole turn).
460
+ // and the stall budget bounds the spin: refusals execute no tools, so the
461
+ // no-progress clock keeps ticking and terminates the turn at JEO_TURN_MAX_MS).
453
462
  const MAX_REFUSAL_RETRIES = GUARD_LIMITS.MAX_REFUSAL_RETRIES;
454
463
  let refusalRetries = 0;
455
464
  let lastSig = "";
@@ -475,9 +484,9 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
475
484
  // schemas ONCE instead of rebuilding/reallocating the list on every step.
476
485
  const turnToolSchemas = nativeToolSchemasFor(Object.keys(tools));
477
486
  while (true) {
478
- if (turnBudgetMs > 0 && Date.now() - turnStartedAt > turnBudgetMs) {
487
+ if (turnBudgetMs > 0 && Date.now() - lastProgressAt > turnBudgetMs) {
479
488
  stopKind = "time";
480
- budgetStopReason = `turn wall-clock budget of ${Math.round(turnBudgetMs / 60_000)}m exceeded (JEO_TURN_MAX_MS) without done`;
489
+ budgetStopReason = `no tool progress for ${Math.round(turnBudgetMs / 60_000)}m — turn stall budget (JEO_TURN_MAX_MS) exceeded without done`;
481
490
  break;
482
491
  }
483
492
  if (step > budget.limit()) {
@@ -515,6 +524,7 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
515
524
  consecutiveFailures = 0;
516
525
  recentStepSigs.length = 0;
517
526
  budget.noteSteer?.();
527
+ lastProgressAt = Date.now(); // fresh instruction = fresh progress window
518
528
  }
519
529
  }
520
530
 
@@ -618,8 +628,9 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
618
628
  // backoff instead of surfacing a terminal error (gjc parity: gjc's
619
629
  // session auto-retry classifies a refusal as retryable and never ends
620
630
  // the turn on it). Esc/cancel aborts the wait via opts.signal and the
621
- // loop-top check turns it into "Cancelled."; JEO_TURN_MAX_MS still
622
- // bounds the whole turn.
631
+ // loop-top check turns it into "Cancelled."; the JEO_TURN_MAX_MS stall
632
+ // budget still bounds the spin (a refusal loop executes no tools, so
633
+ // the no-progress clock never resets).
623
634
  if (isRefusalError(err)) {
624
635
  refusalRetries++;
625
636
  if (refusalRetries === 1) {
@@ -1133,6 +1144,8 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
1133
1144
  // repeated call's ACTUAL last outcome (A). A skipped/bounced step never reaches
1134
1145
  // here, so this always holds the last REAL execution's results.
1135
1146
  lastResults = results;
1147
+ // Executed tool step = progress: the stall budget clocks time WITHOUT this.
1148
+ if (results.some(r => r.executed)) lastProgressAt = Date.now();
1136
1149
  step++;
1137
1150
  }
1138
1151
 
@@ -1143,7 +1156,7 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
1143
1156
  const extInfo = budget.extensionsUsed() > 0 ? ` after ${budget.extensionsUsed()} extension(s)` : "";
1144
1157
  const stopInfo = budgetStopReason ? `; ${budgetStopReason}` : "";
1145
1158
  const budgetLabel = stopKind === "time"
1146
- ? `turn time budget of ${Math.round(turnBudgetMs / 60_000)}m reached`
1159
+ ? `turn time budget of ${Math.round(turnBudgetMs / 60_000)}m reached (no tool progress)`
1147
1160
  : `step budget of ${budget.limit()} reached`;
1148
1161
  try {
1149
1162
  if (!opts.signal?.aborted) {
@@ -126,13 +126,14 @@ export function codexResponsesRequest(
126
126
  // frame can show the model's thinking instead of a frozen "calling model (Ns)…".
127
127
  payload.reasoning = { effort: options.reasoningEffort, summary: "auto" };
128
128
  }
129
- // Cap the response length (gjc parity): the computed per-call budget must reach the wire,
130
- // otherwise the backend free-runs to its own default.
131
- if (options.maxTokens) payload.max_output_tokens = options.maxTokens;
129
+ // Cap the response length (gjc parity) on the PUBLIC Responses API only. The ChatGPT/Codex
130
+ // OAuth backend 400s on this parameter ({"detail":"Unsupported parameter: max_output_tokens"}),
131
+ // so the cap is applied inside the api_key branch below.
132
132
  // OAuth → the undocumented ChatGPT/Codex backend (codex headers + account-id).
133
133
  // API key → the public OpenAI Responses API (`/v1/responses`) with a plain Bearer.
134
134
  // Both speak the same Responses schema (the body above), so only url+headers differ.
135
135
  if (credential.kind === "api_key") {
136
+ if (options.maxTokens) payload.max_output_tokens = options.maxTokens;
136
137
  const base = (options.baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "");
137
138
  // Stateless reasoning replay (public Responses API): ask for encrypted reasoning content
138
139
  // so it can be captured and threaded back into a later `input` (store stays false).