jeo-code 0.6.38 → 0.6.39
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 +11 -0
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/package.json +1 -1
- package/src/ai/model-manager.ts +51 -17
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.39] - 2026-06-21
|
|
10
|
+
_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)._
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **A long "thinking" phase no longer trips a false `stream idle for <ms>ms (no chunk)` retry.** Reasoning/thinking deltas are routed to `onReasoning` and never yielded as a stream chunk, so a model that streams thought tokens for longer than the idle window (default 120s) before emitting visible text looked stalled and got retried — discarding the in-progress reasoning. The per-chunk idle watchdog now treats reasoning activity as a heartbeat (`lastActivityAt`) and re-arms while thinking is actively streaming; it aborts only a genuinely silent stream. The `JEO_STREAM_IDLE_MS` override still covers models that reason fully server-side and stream no thought tokens at all.
|
|
14
|
+
|
|
15
|
+
### Verified
|
|
16
|
+
- `bun run typecheck` clean; full suite **1752 pass / 0 fail** (216 files), including the new `retryableStream` reasoning-heartbeat tests in `test/round-b.test.ts` (a silent-but-thinking stream stays alive; the watchdog still fires once reasoning activity stops).
|
|
17
|
+
- `scripts/mem-probe.ts` (2000 turns × 40 tools): no long-term leak (per-turn slope **−525 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.38] - 2026-06-21
|
|
10
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)._
|
|
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.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).
|
|
203
204
|
- **[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).
|
|
204
205
|
- **[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.
|
|
205
206
|
- **[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.
|
|
206
207
|
- **[0.6.35]** (2026-06-20) — The prompt's Ctrl+C now clears a non-empty input box on the first press and only exits on the next press of an empty box; plus app-driven system-clipboard copy (OSC 52 + local tool, tmux-aware), drag-and-drop image attachment, a Ctrl-L prompt re-anchor, and a SIGCONT resume repaint — verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot check.
|
|
207
|
-
- **[0.6.34]** (2026-06-20) — Per-session model memory — each saved session now remembers the model it was last using and restores it on `/resume` — plus clearer `jeo --tmux` attach diagnostics, a tmux session-name double-dash fix, and a more robust no-leak probe gate.
|
|
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.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).
|
|
203
204
|
- **[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).
|
|
204
205
|
- **[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.
|
|
205
206
|
- **[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.
|
|
206
207
|
- **[0.6.35]** (2026-06-20) — The prompt's Ctrl+C now clears a non-empty input box on the first press and only exits on the next press of an empty box; plus app-driven system-clipboard copy (OSC 52 + local tool, tmux-aware), drag-and-drop image attachment, a Ctrl-L prompt re-anchor, and a SIGCONT resume repaint — verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot check.
|
|
207
|
-
- **[0.6.34]** (2026-06-20) — Per-session model memory — each saved session now remembers the model it was last using and restores it on `/resume` — plus clearer `jeo --tmux` attach diagnostics, a tmux session-name double-dash fix, and a more robust no-leak probe gate.
|
|
208
208
|
|
|
209
209
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
210
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -200,11 +200,11 @@ Required npm token permissions (repository secret `NPM_TOKEN`):
|
|
|
200
200
|
## Changelog
|
|
201
201
|
|
|
202
202
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
203
|
+
- **[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).
|
|
203
204
|
- **[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).
|
|
204
205
|
- **[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.
|
|
205
206
|
- **[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.
|
|
206
207
|
- **[0.6.35]** (2026-06-20) — The prompt's Ctrl+C now clears a non-empty input box on the first press and only exits on the next press of an empty box; plus app-driven system-clipboard copy (OSC 52 + local tool, tmux-aware), drag-and-drop image attachment, a Ctrl-L prompt re-anchor, and a SIGCONT resume repaint — verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot check.
|
|
207
|
-
- **[0.6.34]** (2026-06-20) — Per-session model memory — each saved session now remembers the model it was last using and restores it on `/resume` — plus clearer `jeo --tmux` attach diagnostics, a tmux session-name double-dash fix, and a more robust no-leak probe gate.
|
|
208
208
|
|
|
209
209
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
210
|
<!-- 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.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).
|
|
203
204
|
- **[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).
|
|
204
205
|
- **[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.
|
|
205
206
|
- **[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.
|
|
206
207
|
- **[0.6.35]** (2026-06-20) — The prompt's Ctrl+C now clears a non-empty input box on the first press and only exits on the next press of an empty box; plus app-driven system-clipboard copy (OSC 52 + local tool, tmux-aware), drag-and-drop image attachment, a Ctrl-L prompt re-anchor, and a SIGCONT resume repaint — verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot check.
|
|
207
|
-
- **[0.6.34]** (2026-06-20) — Per-session model memory — each saved session now remembers the model it was last using and restores it on `/resume` — plus clearer `jeo --tmux` attach diagnostics, a tmux session-name double-dash fix, and a more robust no-leak probe gate.
|
|
208
208
|
|
|
209
209
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
210
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
package/src/ai/model-manager.ts
CHANGED
|
@@ -434,30 +434,49 @@ export interface StreamIdleOptions {
|
|
|
434
434
|
* Non-interactive contexts opt in (JEO_STREAM_MAX_MS) so a slow-drip stream
|
|
435
435
|
* (one token every idleMs-ε) cannot run unbounded. */
|
|
436
436
|
deadlineAt?: number;
|
|
437
|
+
/** Epoch-ms of the most recent stream ACTIVITY, including reasoning/thinking deltas
|
|
438
|
+
* that are routed to onReasoning and never yielded as a chunk. A long "thinking"
|
|
439
|
+
* phase that streams thought tokens keeps bumping this, so the per-chunk idle
|
|
440
|
+
* watchdog re-arms instead of falsely aborting an actively-reasoning stream. Absent
|
|
441
|
+
* → only a yielded chunk counts as activity (legacy behaviour). */
|
|
442
|
+
lastActivityAt?: () => number;
|
|
437
443
|
onIdle?: () => void;
|
|
438
444
|
}
|
|
439
445
|
|
|
440
|
-
/** `iter.next()`, racing
|
|
446
|
+
/** `iter.next()`, racing a per-chunk idle watchdog AND (when set) the overall deadline.
|
|
447
|
+
* The watchdog re-arms while reasoning activity (idle.lastActivityAt) keeps advancing,
|
|
448
|
+
* so a model that streams thinking tokens for longer than idleMs before emitting visible
|
|
449
|
+
* text is NOT mistaken for a stalled stream — only a genuinely silent stream aborts. */
|
|
441
450
|
async function nextMaybeIdle(iter: AsyncIterator<string>, idle?: StreamIdleOptions): Promise<IteratorResult<string>> {
|
|
442
451
|
if (!idle) return iter.next();
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
const waitMs = Math.min(idle.idleMs, remaining);
|
|
449
|
-
const deadlineFires = remaining < idle.idleMs;
|
|
452
|
+
const next = iter.next();
|
|
453
|
+
// Baseline: this wait began now; reasoning deltas during it move lastActivityAt forward.
|
|
454
|
+
const startedAt = Date.now();
|
|
455
|
+
const lastActivity = () => Math.max(startedAt, idle.lastActivityAt?.() ?? 0);
|
|
450
456
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
457
|
+
const watchdog = new Promise<never>((_, reject) => {
|
|
458
|
+
const arm = () => {
|
|
459
|
+
const now = Date.now();
|
|
460
|
+
const overallRemaining = idle.deadlineAt !== undefined ? idle.deadlineAt - now : Infinity;
|
|
461
|
+
if (overallRemaining <= 0) {
|
|
462
|
+
idle.onIdle?.();
|
|
463
|
+
reject(new Error(`stream exceeded the overall deadline (JEO_STREAM_MAX_MS) — slow-drip stream aborted`));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const idleRemaining = idle.idleMs - (now - lastActivity());
|
|
467
|
+
const wait = Math.min(idleRemaining, overallRemaining);
|
|
468
|
+
if (wait <= 0) {
|
|
469
|
+
// overallRemaining is > 0 here (pre-checked), so wait<=0 ⟺ the idle window lapsed.
|
|
470
|
+
idle.onIdle?.();
|
|
471
|
+
reject(new Error(`stream idle for ${idle.idleMs}ms (no chunk) — provider sent no token within the idle window (load or long thinking); retrying. Raise JEO_STREAM_IDLE_MS or lower the thinking level if this persists.`));
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
timer = setTimeout(arm, wait);
|
|
475
|
+
};
|
|
476
|
+
arm();
|
|
458
477
|
});
|
|
459
478
|
try {
|
|
460
|
-
return await Promise.race([
|
|
479
|
+
return await Promise.race([next, watchdog]);
|
|
461
480
|
} finally {
|
|
462
481
|
if (timer) clearTimeout(timer);
|
|
463
482
|
}
|
|
@@ -512,15 +531,30 @@ export function createModelManager(): ModelManager {
|
|
|
512
531
|
// JEO_STREAM_MAX_MS opts in to an OVERALL deadline (round-14): non-interactive
|
|
513
532
|
// runs can bound a slow-drip stream the per-chunk idle alone never catches.
|
|
514
533
|
let attempt: AbortController | null = null;
|
|
534
|
+
// Heartbeat: reasoning/thinking deltas are routed to onReasoning and NEVER yielded
|
|
535
|
+
// as a chunk, so a model that "thinks" (streams thought tokens) for longer than the
|
|
536
|
+
// idle window would otherwise look stalled and trip a false idle-stall retry. Wrap
|
|
537
|
+
// onReasoning/onReasoningStart to stamp lastActivityAt; the idle watchdog re-arms
|
|
538
|
+
// while thinking is actively streaming and only aborts a genuinely silent stream.
|
|
539
|
+
let lastActivityAt = Date.now();
|
|
540
|
+
const bump = () => { lastActivityAt = Date.now(); };
|
|
541
|
+
const userOnReasoning = callOptions.onReasoning;
|
|
542
|
+
const userOnReasoningStart = callOptions.onReasoningStart;
|
|
543
|
+
const liveOptions: CallOptions = {
|
|
544
|
+
...callOptions,
|
|
545
|
+
onReasoning: (delta: string) => { bump(); userOnReasoning?.(delta); },
|
|
546
|
+
onReasoningStart: () => { bump(); userOnReasoningStart?.(); },
|
|
547
|
+
};
|
|
515
548
|
const makeIter = () => {
|
|
516
549
|
attempt = new AbortController();
|
|
517
550
|
const signal = composeAbort(callOptions.signal, attempt.signal);
|
|
518
|
-
return streamFn(messages, { ...
|
|
551
|
+
return streamFn(messages, { ...liveOptions, signal }, credential)[Symbol.asyncIterator]();
|
|
519
552
|
};
|
|
520
553
|
const maxMs = streamMaxMs();
|
|
521
554
|
yield* retryableStream(makeIter, retry, {
|
|
522
555
|
idleMs: streamIdleMs(),
|
|
523
556
|
...(maxMs !== undefined ? { deadlineAt: Date.now() + maxMs } : {}),
|
|
557
|
+
lastActivityAt: () => lastActivityAt,
|
|
524
558
|
onIdle: () => attempt?.abort(),
|
|
525
559
|
});
|
|
526
560
|
} else {
|