jeo-code 0.6.40 → 0.6.41

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.6.41] - 2026-06-21
10
+ _The non-streaming model path now inherits the v0.6.40 300s window: an unattended long-reasoning turn (compaction, ralplan, deep-interview, memory distill, goal-verify, subagent/autopilot steps) is no longer false-aborted at the old 120s wall cap. Re-verified leak-free (`mem-probe`, 2000 turns, −610 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6)._
11
+
12
+ ### Fixed
13
+ - **The non-streaming `call()` path no longer false-aborts a long completion at 120s — it now matches the 300s streaming idle window.** v0.6.40's wire heartbeat only re-armed the streaming watchdog (`manager.stream()`); the non-streaming twin `manager.call()` — taken by every `callLlm` WITHOUT an `onToken` consumer (`compaction`, `ralplan`, `deep-interview`, `memory` distill, `goal-verifier`, and subagent/autopilot engine steps) — kept a hard 120s wall cap, so the exact unattended long-reasoning turns the release meant to protect still aborted, exhausted the attempt budget, and stopped the turn. The cap is now 300s, matching `STREAM_IDLE_TIMEOUT_MS`, via a new env-overridable `callTimeoutMs()` helper (`JEO_CALL_TIMEOUT_MS`) mirroring the existing `streamIdleMs()`. A wire heartbeat can't help here — the path collects an opaque buffered body with no per-chunk signal — so a wall clock is the only correct lever; non-streaming `readSse` sites and `call()`'s buffered reads are deliberately left without an idle watchdog (it would false-abort a healthy opaque read).
14
+
15
+ ### Verified
16
+ - `bun run typecheck` clean; full suite **1756 pass / 0 fail** (216 files), including the new `callTimeoutMs` default + `JEO_CALL_TIMEOUT_MS` override parsing tests in `test/round-b.test.ts`.
17
+ - `scripts/mem-probe.ts` (2000 turns × 40 tools): no long-term leak (per-turn slope **−610 bytes/turn**, exit-listeners flat at 1, heap returns to baseline).
18
+ - `scripts/tmux-verify.sh battery`: **6/6 PASSED** on a fresh `jeo --tmux` boot (boot, `/help`, unknown `$skill`, `/agents`, `$ultragoal`, unresolved `/command`).
19
+
9
20
  ## [0.6.40] - 2026-06-21
10
21
  _Wire-level stream heartbeat: ANY bytes from the provider (SSE keepalive/ping, filtered events) now re-arm the idle watchdog — a connected-but-quiet stream can no longer trip a false `stream idle` retry. Default idle window raised to 300s so Ollama/llama.cpp model-load silence no longer exhausts retries and stops the turn._
11
22
 
package/README.ja.md CHANGED
@@ -200,11 +200,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
200
200
  ## 変更履歴 (Changelog)
201
201
 
202
202
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
203
+ - **[0.6.41]** (2026-06-21) — The non-streaming model path now inherits the v0.6.40 300s window: an unattended long-reasoning turn (compaction, ralplan, deep-interview, memory distill, goal-verify, subagent/autopilot steps) is no longer false-aborted at the old 120s wall cap. Re-verified leak-free (`mem-probe`, 2000 turns, −610 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
203
204
  - **[0.6.40]** (2026-06-21) — Wire-level stream heartbeat: ANY bytes from the provider (SSE keepalive/ping, filtered events) now re-arm the idle watchdog — a connected-but-quiet stream can no longer trip a false `stream idle` retry. Default idle window raised to 300s so Ollama/llama.cpp model-load silence no longer exhausts retries and stops the turn.
204
205
  - **[0.6.39]** (2026-06-21) — A long "thinking" phase no longer trips a false stream-idle retry: reasoning/thinking deltas now act as a stream heartbeat, so a model that streams thought tokens past the idle window before any visible text is no longer mistaken for a stalled stream and retried (which discarded the in-progress reasoning). Re-verified leak-free (`mem-probe`, 2000 turns, −525 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
205
206
  - **[0.6.38]** (2026-06-21) — The OKF concept-bundle memory no longer silently drops what it learns: a legacy single-doc `MEMORY.md` (or a text-only distill fallback) can no longer shadow the concept bundle, break OKF conformance, or lose a turn's learnings — its content is folded into the concept merge and the stale blob is archived. Re-verified leak-free (`mem-probe`, 2000 turns, −570 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
206
207
  - **[0.6.37]** (2026-06-20) — Two dead-end fixes: the boxed prompt's ↑/↓ now recalls input history on a soft-wrapped one-liner (only a genuine multi-line draft gets in-box caret nav), and every terminating Spec-first stage (deep-interview, ralplan, team) now surfaces a user-visible answer instead of silently stalling — re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
207
- - **[0.6.36]** (2026-06-20) — When `jeo --tmux` flips the mouse on so you can drag-select, the drag now actually lands on the system clipboard — the in-session tmux profile sets `set-clipboard on` + a local `copy-command` on the CURRENT session only — plus `/help` documents the drag-to-copy and the Shift/Option-drag escape hatch, re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
208
208
 
209
209
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
210
210
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -200,11 +200,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
200
200
  ## 변경 이력 (Changelog)
201
201
 
202
202
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
203
+ - **[0.6.41]** (2026-06-21) — The non-streaming model path now inherits the v0.6.40 300s window: an unattended long-reasoning turn (compaction, ralplan, deep-interview, memory distill, goal-verify, subagent/autopilot steps) is no longer false-aborted at the old 120s wall cap. Re-verified leak-free (`mem-probe`, 2000 turns, −610 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
203
204
  - **[0.6.40]** (2026-06-21) — Wire-level stream heartbeat: ANY bytes from the provider (SSE keepalive/ping, filtered events) now re-arm the idle watchdog — a connected-but-quiet stream can no longer trip a false `stream idle` retry. Default idle window raised to 300s so Ollama/llama.cpp model-load silence no longer exhausts retries and stops the turn.
204
205
  - **[0.6.39]** (2026-06-21) — A long "thinking" phase no longer trips a false stream-idle retry: reasoning/thinking deltas now act as a stream heartbeat, so a model that streams thought tokens past the idle window before any visible text is no longer mistaken for a stalled stream and retried (which discarded the in-progress reasoning). Re-verified leak-free (`mem-probe`, 2000 turns, −525 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
205
206
  - **[0.6.38]** (2026-06-21) — The OKF concept-bundle memory no longer silently drops what it learns: a legacy single-doc `MEMORY.md` (or a text-only distill fallback) can no longer shadow the concept bundle, break OKF conformance, or lose a turn's learnings — its content is folded into the concept merge and the stale blob is archived. Re-verified leak-free (`mem-probe`, 2000 turns, −570 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
206
207
  - **[0.6.37]** (2026-06-20) — Two dead-end fixes: the boxed prompt's ↑/↓ now recalls input history on a soft-wrapped one-liner (only a genuine multi-line draft gets in-box caret nav), and every terminating Spec-first stage (deep-interview, ralplan, team) now surfaces a user-visible answer instead of silently stalling — re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
207
- - **[0.6.36]** (2026-06-20) — When `jeo --tmux` flips the mouse on so you can drag-select, the drag now actually lands on the system clipboard — the in-session tmux profile sets `set-clipboard on` + a local `copy-command` on the CURRENT session only — plus `/help` documents the drag-to-copy and the Shift/Option-drag escape hatch, re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
208
208
 
209
209
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
210
210
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -201,11 +201,11 @@ Required npm token permissions (repository secret `NPM_TOKEN`):
201
201
  ## Changelog
202
202
 
203
203
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
204
+ - **[0.6.41]** (2026-06-21) — The non-streaming model path now inherits the v0.6.40 300s window: an unattended long-reasoning turn (compaction, ralplan, deep-interview, memory distill, goal-verify, subagent/autopilot steps) is no longer false-aborted at the old 120s wall cap. Re-verified leak-free (`mem-probe`, 2000 turns, −610 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
204
205
  - **[0.6.40]** (2026-06-21) — Wire-level stream heartbeat: ANY bytes from the provider (SSE keepalive/ping, filtered events) now re-arm the idle watchdog — a connected-but-quiet stream can no longer trip a false `stream idle` retry. Default idle window raised to 300s so Ollama/llama.cpp model-load silence no longer exhausts retries and stops the turn.
205
206
  - **[0.6.39]** (2026-06-21) — A long "thinking" phase no longer trips a false stream-idle retry: reasoning/thinking deltas now act as a stream heartbeat, so a model that streams thought tokens past the idle window before any visible text is no longer mistaken for a stalled stream and retried (which discarded the in-progress reasoning). Re-verified leak-free (`mem-probe`, 2000 turns, −525 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
206
207
  - **[0.6.38]** (2026-06-21) — The OKF concept-bundle memory no longer silently drops what it learns: a legacy single-doc `MEMORY.md` (or a text-only distill fallback) can no longer shadow the concept bundle, break OKF conformance, or lose a turn's learnings — its content is folded into the concept merge and the stale blob is archived. Re-verified leak-free (`mem-probe`, 2000 turns, −570 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
207
208
  - **[0.6.37]** (2026-06-20) — Two dead-end fixes: the boxed prompt's ↑/↓ now recalls input history on a soft-wrapped one-liner (only a genuine multi-line draft gets in-box caret nav), and every terminating Spec-first stage (deep-interview, ralplan, team) now surfaces a user-visible answer instead of silently stalling — re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
208
- - **[0.6.36]** (2026-06-20) — When `jeo --tmux` flips the mouse on so you can drag-select, the drag now actually lands on the system clipboard — the in-session tmux profile sets `set-clipboard on` + a local `copy-command` on the CURRENT session only — plus `/help` documents the drag-to-copy and the Shift/Option-drag escape hatch, re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
209
209
 
210
210
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
211
211
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -200,11 +200,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
200
200
  ## 更新日志 (Changelog)
201
201
 
202
202
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
203
+ - **[0.6.41]** (2026-06-21) — The non-streaming model path now inherits the v0.6.40 300s window: an unattended long-reasoning turn (compaction, ralplan, deep-interview, memory distill, goal-verify, subagent/autopilot steps) is no longer false-aborted at the old 120s wall cap. Re-verified leak-free (`mem-probe`, 2000 turns, −610 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
203
204
  - **[0.6.40]** (2026-06-21) — Wire-level stream heartbeat: ANY bytes from the provider (SSE keepalive/ping, filtered events) now re-arm the idle watchdog — a connected-but-quiet stream can no longer trip a false `stream idle` retry. Default idle window raised to 300s so Ollama/llama.cpp model-load silence no longer exhausts retries and stops the turn.
204
205
  - **[0.6.39]** (2026-06-21) — A long "thinking" phase no longer trips a false stream-idle retry: reasoning/thinking deltas now act as a stream heartbeat, so a model that streams thought tokens past the idle window before any visible text is no longer mistaken for a stalled stream and retried (which discarded the in-progress reasoning). Re-verified leak-free (`mem-probe`, 2000 turns, −525 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
205
206
  - **[0.6.38]** (2026-06-21) — The OKF concept-bundle memory no longer silently drops what it learns: a legacy single-doc `MEMORY.md` (or a text-only distill fallback) can no longer shadow the concept bundle, break OKF conformance, or lose a turn's learnings — its content is folded into the concept merge and the stale blob is archived. Re-verified leak-free (`mem-probe`, 2000 turns, −570 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
206
207
  - **[0.6.37]** (2026-06-20) — Two dead-end fixes: the boxed prompt's ↑/↓ now recalls input history on a soft-wrapped one-liner (only a genuine multi-line draft gets in-box caret nav), and every terminating Spec-first stage (deep-interview, ralplan, team) now surfaces a user-visible answer instead of silently stalling — re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
207
- - **[0.6.36]** (2026-06-20) — When `jeo --tmux` flips the mouse on so you can drag-select, the drag now actually lands on the system clipboard — the in-session tmux profile sets `set-clipboard on` + a local `copy-command` on the CURRENT session only — plus `/help` documents the drag-to-copy and the Shift/Option-drag escape hatch, re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
208
208
 
209
209
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
210
210
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.6.40",
3
+ "version": "0.6.41",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -126,6 +126,7 @@ export const ConfigSchema = z
126
126
  plan: z.string().optional(),
127
127
  })
128
128
  .optional(),
129
+ gitAutoCommit: z.boolean().optional(),
129
130
  hooks: HookConfigSchema.optional(),
130
131
  })
131
132
  .passthrough();
@@ -95,6 +95,7 @@ export interface Config {
95
95
  * `defaultModel`. Env `JEO_SMOL_MODEL`/`JEO_SLOW_MODEL`/`JEO_PLAN_MODEL` fill gaps.
96
96
  */
97
97
  roles?: { smol?: string; slow?: string; plan?: string };
98
+ gitAutoCommit?: boolean;
98
99
  hooks?: HookConfig;
99
100
  }
100
101
 
@@ -383,9 +383,18 @@ async function resolveCall(options: Partial<CallOptions>, kind: "request" | "str
383
383
  return { adapter, callOptions, credential: credentialForCall(provider, effective, config, baseUrl), retry };
384
384
  }
385
385
 
386
- /** Hard cap for a single non-streaming provider request (service-readiness: a
387
- * blackholed/unreachable provider must not hang the agent or `jeo team`). */
388
- const DEFAULT_CALL_TIMEOUT_MS = 120_000;
386
+ /** Hard wall-clock cap for a single NON-streaming provider request (service-readiness: a
387
+ * blackholed/unreachable provider must not hang the agent or `jeo team`). Unlike the
388
+ * streaming idle watchdog, this path collects an opaque buffered body (`response.json()`,
389
+ * or an internally-streamed collect in codexResponsesCall / antigravity.call) and exposes
390
+ * no per-chunk signal — so a wall clock is the only lever and a wire heartbeat cannot help.
391
+ * Raised to 300s to match STREAM_IDLE_TIMEOUT_MS: non-interactive turns (callLlm WITHOUT
392
+ * onToken — compaction, ralplan, deep-interview, memory distill, goal-verify, and subagent/
393
+ * autopilot engine steps) route here, and a long reasoning completion legitimately exceeds
394
+ * 120s; too-tight, it aborts an alive call, retries re-incur the same slow request, the
395
+ * attempt budget exhausts, and the turn STOPS — the same false-failure the streaming
396
+ * watchdog guards, on the path the wire heartbeat never reaches. */
397
+ const DEFAULT_CALL_TIMEOUT_MS = 300_000;
389
398
 
390
399
  /** Per-chunk idle cap for streaming: a stream that emits NOTHING for this long is
391
400
  * aborted, but a healthy long generation (chunks keep arriving) runs unbounded —
@@ -505,6 +514,15 @@ export function streamIdleMs(env?: Record<string, string | undefined>): number {
505
514
  return Number.isFinite(n) && n > 0 ? n : STREAM_IDLE_TIMEOUT_MS;
506
515
  }
507
516
 
517
+ /** Hard wall-clock cap (ms) for a NON-streaming provider call, falling back to the
518
+ * built-in default. Mirrors streamIdleMs so a slow non-interactive turn (callLlm without
519
+ * onToken) can relax the cap via JEO_CALL_TIMEOUT_MS without a code change. */
520
+ export function callTimeoutMs(env?: Record<string, string | undefined>): number {
521
+ const raw = jeoEnv("CALL_TIMEOUT_MS", env);
522
+ const n = raw !== undefined ? parseInt(raw, 10) : NaN;
523
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_CALL_TIMEOUT_MS;
524
+ }
525
+
508
526
  export async function* retryableStream(
509
527
  makeIter: () => AsyncIterator<string>,
510
528
  retry: RetryOptions,
@@ -526,7 +544,7 @@ export function createModelManager(): ModelManager {
526
544
  resolveProvider,
527
545
  async call(messages, options = {}) {
528
546
  const { adapter, callOptions, credential, retry } = await resolveCall(options);
529
- return withRetry(() => adapter.call(messages, { ...callOptions, signal: withTimeout(callOptions.signal, DEFAULT_CALL_TIMEOUT_MS) }, credential), retry);
547
+ return withRetry(() => adapter.call(messages, { ...callOptions, signal: withTimeout(callOptions.signal, callTimeoutMs()) }, credential), retry);
530
548
  },
531
549
  async *stream(messages, options = {}) {
532
550
  const { adapter, callOptions, credential, retry } = await resolveCall(options, "stream");
@@ -572,7 +590,7 @@ export function createModelManager(): ModelManager {
572
590
  });
573
591
  } else {
574
592
  // Fallback: providers without streaming yield the full response as one chunk.
575
- yield await withRetry(() => adapter.call(messages, { ...callOptions, signal: withTimeout(callOptions.signal, DEFAULT_CALL_TIMEOUT_MS) }, credential), retry);
593
+ yield await withRetry(() => adapter.call(messages, { ...callOptions, signal: withTimeout(callOptions.signal, callTimeoutMs()) }, credential), retry);
576
594
  }
577
595
  },
578
596
  };
@@ -1025,6 +1025,31 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
1025
1025
  }
1026
1026
  sessionUsage.turns++;
1027
1027
  const usage = result.usage ? ` (${result.usage.inputTokens} in / ${result.usage.outputTokens} out tokens)` : "";
1028
+
1029
+ if (turnConfig.gitAutoCommit) {
1030
+ try {
1031
+ const statusRes = Bun.spawnSync(["git", "status", "--porcelain"], { cwd, stdout: "pipe", stderr: "ignore" });
1032
+ if (statusRes.exitCode === 0 && statusRes.stdout.toString().trim().length > 0) {
1033
+ Bun.spawnSync(["git", "add", "."], { cwd });
1034
+ const diffRes = Bun.spawnSync(["git", "diff", "--cached", "--name-status"], { cwd, stdout: "pipe", stderr: "ignore" });
1035
+ const changedFiles = diffRes.stdout.toString().trim().split("\n").map(line => line.split("\t")[1]).filter(Boolean);
1036
+ const fileSummary = changedFiles.length > 0 ? ` (modified: ${changedFiles.slice(0, 3).join(", ")}${changedFiles.length > 3 ? ", ..." : ""})` : "";
1037
+ const commitMsg = `[jeo] auto-commit${fileSummary}\n\nPrompt: ${userInput.slice(0, 100)}${userInput.length > 100 ? "..." : ""}`;
1038
+ const commitRes = Bun.spawnSync(["git", "commit", "-m", commitMsg], { cwd });
1039
+ if (commitRes.exitCode === 0) {
1040
+ if (tui) tui.events().onNotice?.(`(auto-committed changes to git)`);
1041
+ else console.log(`(auto-committed changes to git)`);
1042
+ } else {
1043
+ if (tui) tui.events().onNotice?.(`(auto-commit failed: git commit returned non-zero)`);
1044
+ else console.error(`(auto-commit failed: git commit returned non-zero)`);
1045
+ }
1046
+ }
1047
+ } catch (e) {
1048
+ if (tui) tui.events().onNotice?.(`(auto-commit failed: ${e instanceof Error ? e.message : String(e)})`);
1049
+ else console.error(`(auto-commit failed: ${e instanceof Error ? e.message : String(e)})`);
1050
+ }
1051
+ }
1052
+
1028
1053
  return { done: result.done, steps: result.steps, reply, rendered: !!tui, usage };
1029
1054
  };
1030
1055
 
@@ -3349,6 +3374,29 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
3349
3374
  }
3350
3375
  continue;
3351
3376
  }
3377
+ if (input === "/undo") {
3378
+ try {
3379
+ const logRes = Bun.spawnSync(["git", "log", "-1", "--pretty=%B"], { cwd, stdout: "pipe", stderr: "ignore" });
3380
+ if (logRes.exitCode === 0) {
3381
+ const msg = logRes.stdout.toString().trim();
3382
+ if (msg.startsWith("[jeo] auto-commit:")) {
3383
+ const resetRes = Bun.spawnSync(["git", "reset", "--hard", "HEAD~1"], { cwd, stdout: "pipe", stderr: "pipe" });
3384
+ if (resetRes.exitCode === 0) {
3385
+ console.log("(undid the last jeo auto-commit and restored the working tree)");
3386
+ } else {
3387
+ console.log(`! undo failed: ${resetRes.stderr.toString().trim()}`);
3388
+ }
3389
+ } else {
3390
+ console.log("! the last commit was not made by jeo (no '[jeo] auto-commit:' prefix). Use git manually to revert.");
3391
+ }
3392
+ } else {
3393
+ console.log("! undo failed: could not read git log (not a git repo?)");
3394
+ }
3395
+ } catch (err) {
3396
+ console.log(`! undo failed: ${(err as Error).message}`);
3397
+ }
3398
+ continue;
3399
+ }
3352
3400
  if (input === "/goal" || input.startsWith("/goal ")) {
3353
3401
  const arg = input.substring(5).trim();
3354
3402
  if (!arg) {