jeo-code 0.7.7 → 0.7.8
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 +13 -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 +3 -2
- package/src/agent/memory.ts +28 -12
- package/src/commands/launch.ts +10 -2
- package/src/commands/team.ts +2 -1
- package/src/tui/app.ts +19 -6
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ 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.8] - 2026-06-24
|
|
10
|
+
_Long-session performance & resource hygiene: the detached memory-distill worker now always terminates itself (closing the `jeo memory-distill` orphan pileup that pinned each session's transcript in RSS), the live reasoning/output block re-wraps only its trailing window (O(tail) per frame instead of O(len²) over a long stream), the team loop stops reprinting the todo guide every iteration, and the readline caret stays aligned after an image attach._
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **The detached memory-distill worker always terminates itself.** `runMemoryDistillCommand` now wraps its work in `try/finally` and calls an injected `exit` (defaulting to `process.exit`) on every path — success, broken payload, and missing payload. `distillSessionMemory` caps the LLM call with a `Promise.race` timeout, but the race only *rejects*; it never aborts the underlying fetch, so a stalled provider socket kept the child's event loop alive and the `jeo memory-distill` worker lingered forever — one orphan per session, each pinning the full transcript in RSS (the observed "jeo bun" CPU/memory creep). An explicit exit closes the socket and reclaims the process.
|
|
14
|
+
- **The live reasoning/output block re-wraps only its trailing window.** The wrap memo in `app.ts` keyed on the full (up-to-hundreds-of-KB) buffer, so every 120 ms tick and every stream delta copied + compared the whole growing string — O(len) per frame, O(len²) over a long reasoning/tool stream (the streaming slowdown). `liveBlockWrapKey` now slices the ≤16 KB tail first, then keys + wraps that: identical visible output, O(tail) per frame regardless of total size.
|
|
15
|
+
- **The team loop no longer reprints the todo guide every iteration.** `runTeamEngine` printed `formatRalphTodoGuide` once per task — O(N²) lines that climbed the embedded-terminal renderer's CPU on a long task list. The guide prints once up front; per-task progress lines carry the rest.
|
|
16
|
+
- **The readline caret stays aligned after an image attach.** Both the paste and Ctrl+V image-attach paths now call readline's internal `_refreshLine()` after swapping the `[image #n]` tag in (mirroring the slash/tab-completion accept paths), so the stale row/cursor model no longer offsets the real caret several columns on the next keystroke.
|
|
17
|
+
|
|
18
|
+
### Verified
|
|
19
|
+
- `bun run typecheck` clean; full suite green — 1914 pass / 0 fail across 229 files — including the new `runMemoryDistillCommand` always-exits assertions (success + no-payload paths) and the `liveBlockWrapKey` bounded-key / tail-collision tests.
|
|
20
|
+
|
|
21
|
+
|
|
9
22
|
## [0.7.7] - 2026-06-23
|
|
10
23
|
_Long-running-process hygiene & input robustness: backgrounded grandchildren (`next dev &`, daemons) are now reaped at the turn boundary via per-command process groups — closing the climbing `next-server` RSS leak — while the prompt gains recursive fuzzy `@path` search, ANSI-safe and idle-merged bracketed paste, a configurable OSC 52 clipboard cap, per-role ralplan model routing, and leaked-reasoning-tag stripping on salvaged answers._
|
|
11
24
|
|
package/README.ja.md
CHANGED
|
@@ -193,11 +193,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
|
|
|
193
193
|
## 変更履歴 (Changelog)
|
|
194
194
|
|
|
195
195
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
196
|
+
- **[0.7.8]** (2026-06-24) — Long-session performance & resource hygiene: the detached memory-distill worker now always terminates itself (closing the `jeo memory-distill` orphan pileup that pinned each session's transcript in RSS), the live reasoning/output block re-wraps only its trailing window (O(tail) per frame instead of O(len²) over a long stream), the team loop stops reprinting the todo guide every iteration, and the readline caret stays aligned after an image attach.
|
|
196
197
|
- **[0.7.7]** (2026-06-23) — Long-running-process hygiene & input robustness: backgrounded grandchildren (`next dev &`, daemons) are now reaped at the turn boundary via per-command process groups — closing the climbing `next-server` RSS leak — while the prompt gains recursive fuzzy `@path` search, ANSI-safe and idle-merged bracketed paste, a configurable OSC 52 clipboard cap, per-role ralplan model routing, and leaked-reasoning-tag stripping on salvaged answers.
|
|
197
198
|
- **[0.7.6]** (2026-06-23) — Release & docs plumbing: the running version's release notes now travel inside the `--compile` single binary (so `/whats-new` works offline and from a global install), the in-TUI markdown table renderer stops splitting on escaped `\|` pipes, and the release pipeline gains a cross-compiled standalone-binary matrix — macOS/Linux **plus a first-class Windows x64 `.exe`** — built on one Linux runner and auto-attached to each GitHub release.
|
|
198
199
|
- **[0.7.5]** (2026-06-23) — Startup & loop latency: redundant re-reads that fired on every subagent spawn and every loop step are now memoized behind cheap `stat`-signature caches, so cold paths stay correct (edits/deletes still picked up immediately) while warm paths skip the disk and the re-parse. Targets the per-turn and per-spawn overhead that dominates perceived slowness in team/ralph/autopilot fan-outs.
|
|
199
200
|
- **[0.7.4]** (2026-06-22) — Per-session model isolation + REPL slash-handler testability: a running `jeo` session now pins the model it resolved at start, so a concurrent session running `/model` (which persists the global default) can no longer silently switch a different live session's model mid-run. The read-only code-inspection slashes (`/view`, `/diff`, `/find`, `/search`), the `/undo` git slash, and the `/history` view are extracted into pure, PTY-free handlers so their logic is unit-tested directly instead of buried in the REPL loop.
|
|
200
|
-
- **[0.7.3]** (2026-06-22) — Provider catalog: the Tencent Cloud MaaS (international) `knownModels` list is broadened from a 4-id DeepSeek/MiniMax set to the full live-verified line-up across five families — DeepSeek, MiniMax, Zhipu GLM, Moonshot Kimi, and Hunyuan. Because the host exposes no `/v1/models` route, this hand-maintained list is the model picker's source of truth for the offline fallback.
|
|
201
201
|
|
|
202
202
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
203
203
|
<!-- CHANGELOG:END -->
|
package/README.ko.md
CHANGED
|
@@ -193,11 +193,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
|
|
|
193
193
|
## 변경 이력 (Changelog)
|
|
194
194
|
|
|
195
195
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
196
|
+
- **[0.7.8]** (2026-06-24) — Long-session performance & resource hygiene: the detached memory-distill worker now always terminates itself (closing the `jeo memory-distill` orphan pileup that pinned each session's transcript in RSS), the live reasoning/output block re-wraps only its trailing window (O(tail) per frame instead of O(len²) over a long stream), the team loop stops reprinting the todo guide every iteration, and the readline caret stays aligned after an image attach.
|
|
196
197
|
- **[0.7.7]** (2026-06-23) — Long-running-process hygiene & input robustness: backgrounded grandchildren (`next dev &`, daemons) are now reaped at the turn boundary via per-command process groups — closing the climbing `next-server` RSS leak — while the prompt gains recursive fuzzy `@path` search, ANSI-safe and idle-merged bracketed paste, a configurable OSC 52 clipboard cap, per-role ralplan model routing, and leaked-reasoning-tag stripping on salvaged answers.
|
|
197
198
|
- **[0.7.6]** (2026-06-23) — Release & docs plumbing: the running version's release notes now travel inside the `--compile` single binary (so `/whats-new` works offline and from a global install), the in-TUI markdown table renderer stops splitting on escaped `\|` pipes, and the release pipeline gains a cross-compiled standalone-binary matrix — macOS/Linux **plus a first-class Windows x64 `.exe`** — built on one Linux runner and auto-attached to each GitHub release.
|
|
198
199
|
- **[0.7.5]** (2026-06-23) — Startup & loop latency: redundant re-reads that fired on every subagent spawn and every loop step are now memoized behind cheap `stat`-signature caches, so cold paths stay correct (edits/deletes still picked up immediately) while warm paths skip the disk and the re-parse. Targets the per-turn and per-spawn overhead that dominates perceived slowness in team/ralph/autopilot fan-outs.
|
|
199
200
|
- **[0.7.4]** (2026-06-22) — Per-session model isolation + REPL slash-handler testability: a running `jeo` session now pins the model it resolved at start, so a concurrent session running `/model` (which persists the global default) can no longer silently switch a different live session's model mid-run. The read-only code-inspection slashes (`/view`, `/diff`, `/find`, `/search`), the `/undo` git slash, and the `/history` view are extracted into pure, PTY-free handlers so their logic is unit-tested directly instead of buried in the REPL loop.
|
|
200
|
-
- **[0.7.3]** (2026-06-22) — Provider catalog: the Tencent Cloud MaaS (international) `knownModels` list is broadened from a 4-id DeepSeek/MiniMax set to the full live-verified line-up across five families — DeepSeek, MiniMax, Zhipu GLM, Moonshot Kimi, and Hunyuan. Because the host exposes no `/v1/models` route, this hand-maintained list is the model picker's source of truth for the offline fallback.
|
|
201
201
|
|
|
202
202
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
203
203
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -195,11 +195,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
|
|
|
195
195
|
## Changelog
|
|
196
196
|
|
|
197
197
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
198
|
+
- **[0.7.8]** (2026-06-24) — Long-session performance & resource hygiene: the detached memory-distill worker now always terminates itself (closing the `jeo memory-distill` orphan pileup that pinned each session's transcript in RSS), the live reasoning/output block re-wraps only its trailing window (O(tail) per frame instead of O(len²) over a long stream), the team loop stops reprinting the todo guide every iteration, and the readline caret stays aligned after an image attach.
|
|
198
199
|
- **[0.7.7]** (2026-06-23) — Long-running-process hygiene & input robustness: backgrounded grandchildren (`next dev &`, daemons) are now reaped at the turn boundary via per-command process groups — closing the climbing `next-server` RSS leak — while the prompt gains recursive fuzzy `@path` search, ANSI-safe and idle-merged bracketed paste, a configurable OSC 52 clipboard cap, per-role ralplan model routing, and leaked-reasoning-tag stripping on salvaged answers.
|
|
199
200
|
- **[0.7.6]** (2026-06-23) — Release & docs plumbing: the running version's release notes now travel inside the `--compile` single binary (so `/whats-new` works offline and from a global install), the in-TUI markdown table renderer stops splitting on escaped `\|` pipes, and the release pipeline gains a cross-compiled standalone-binary matrix — macOS/Linux **plus a first-class Windows x64 `.exe`** — built on one Linux runner and auto-attached to each GitHub release.
|
|
200
201
|
- **[0.7.5]** (2026-06-23) — Startup & loop latency: redundant re-reads that fired on every subagent spawn and every loop step are now memoized behind cheap `stat`-signature caches, so cold paths stay correct (edits/deletes still picked up immediately) while warm paths skip the disk and the re-parse. Targets the per-turn and per-spawn overhead that dominates perceived slowness in team/ralph/autopilot fan-outs.
|
|
201
202
|
- **[0.7.4]** (2026-06-22) — Per-session model isolation + REPL slash-handler testability: a running `jeo` session now pins the model it resolved at start, so a concurrent session running `/model` (which persists the global default) can no longer silently switch a different live session's model mid-run. The read-only code-inspection slashes (`/view`, `/diff`, `/find`, `/search`), the `/undo` git slash, and the `/history` view are extracted into pure, PTY-free handlers so their logic is unit-tested directly instead of buried in the REPL loop.
|
|
202
|
-
- **[0.7.3]** (2026-06-22) — Provider catalog: the Tencent Cloud MaaS (international) `knownModels` list is broadened from a 4-id DeepSeek/MiniMax set to the full live-verified line-up across five families — DeepSeek, MiniMax, Zhipu GLM, Moonshot Kimi, and Hunyuan. Because the host exposes no `/v1/models` route, this hand-maintained list is the model picker's source of truth for the offline fallback.
|
|
203
203
|
|
|
204
204
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
205
205
|
<!-- CHANGELOG:END -->
|
package/README.zh.md
CHANGED
|
@@ -193,11 +193,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
|
|
|
193
193
|
## 更新日志 (Changelog)
|
|
194
194
|
|
|
195
195
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
196
|
+
- **[0.7.8]** (2026-06-24) — Long-session performance & resource hygiene: the detached memory-distill worker now always terminates itself (closing the `jeo memory-distill` orphan pileup that pinned each session's transcript in RSS), the live reasoning/output block re-wraps only its trailing window (O(tail) per frame instead of O(len²) over a long stream), the team loop stops reprinting the todo guide every iteration, and the readline caret stays aligned after an image attach.
|
|
196
197
|
- **[0.7.7]** (2026-06-23) — Long-running-process hygiene & input robustness: backgrounded grandchildren (`next dev &`, daemons) are now reaped at the turn boundary via per-command process groups — closing the climbing `next-server` RSS leak — while the prompt gains recursive fuzzy `@path` search, ANSI-safe and idle-merged bracketed paste, a configurable OSC 52 clipboard cap, per-role ralplan model routing, and leaked-reasoning-tag stripping on salvaged answers.
|
|
197
198
|
- **[0.7.6]** (2026-06-23) — Release & docs plumbing: the running version's release notes now travel inside the `--compile` single binary (so `/whats-new` works offline and from a global install), the in-TUI markdown table renderer stops splitting on escaped `\|` pipes, and the release pipeline gains a cross-compiled standalone-binary matrix — macOS/Linux **plus a first-class Windows x64 `.exe`** — built on one Linux runner and auto-attached to each GitHub release.
|
|
198
199
|
- **[0.7.5]** (2026-06-23) — Startup & loop latency: redundant re-reads that fired on every subagent spawn and every loop step are now memoized behind cheap `stat`-signature caches, so cold paths stay correct (edits/deletes still picked up immediately) while warm paths skip the disk and the re-parse. Targets the per-turn and per-spawn overhead that dominates perceived slowness in team/ralph/autopilot fan-outs.
|
|
199
200
|
- **[0.7.4]** (2026-06-22) — Per-session model isolation + REPL slash-handler testability: a running `jeo` session now pins the model it resolved at start, so a concurrent session running `/model` (which persists the global default) can no longer silently switch a different live session's model mid-run. The read-only code-inspection slashes (`/view`, `/diff`, `/find`, `/search`), the `/undo` git slash, and the `/history` view are extracted into pure, PTY-free handlers so their logic is unit-tested directly instead of buried in the REPL loop.
|
|
200
|
-
- **[0.7.3]** (2026-06-22) — Provider catalog: the Tencent Cloud MaaS (international) `knownModels` list is broadened from a 4-id DeepSeek/MiniMax set to the full live-verified line-up across five families — DeepSeek, MiniMax, Zhipu GLM, Moonshot Kimi, and Hunyuan. Because the host exposes no `/v1/models` route, this hand-maintained list is the model picker's source of truth for the offline fallback.
|
|
201
201
|
|
|
202
202
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
203
203
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jeo-code",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
4
4
|
"description": "Clean, highly optimized AI coding agent using spec-first loop",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/cli.ts",
|
|
@@ -64,5 +64,6 @@
|
|
|
64
64
|
"playwright": "^1.61.0",
|
|
65
65
|
"typescript": "^5.6.3",
|
|
66
66
|
"ws": "^8.21.0"
|
|
67
|
-
}
|
|
67
|
+
},
|
|
68
|
+
"packageManager": "pnpm@10.33.4+sha512.1c67b3b359b2d408119ba1ed289f34b8fc3c6873412bec6fd264fbdc82489e510fcbecb9ce9d22dae7f3b76269d8441046014bdca53b9979cd7a561ad631b800"
|
|
68
69
|
}
|
package/src/agent/memory.ts
CHANGED
|
@@ -876,21 +876,37 @@ export async function spawnDetachedDistill(
|
|
|
876
876
|
}
|
|
877
877
|
|
|
878
878
|
/** CLI worker for the detached child: payload → distill → cleanup. Silent by design. */
|
|
879
|
-
export async function runMemoryDistillCommand(
|
|
880
|
-
|
|
881
|
-
|
|
879
|
+
export async function runMemoryDistillCommand(
|
|
880
|
+
args: string[],
|
|
881
|
+
// Injected for tests; the REAL detached worker must terminate ITSELF. distillSessionMemory
|
|
882
|
+
// caps the LLM call with a Promise.race timeout, but the race only REJECTS — it never aborts
|
|
883
|
+
// the underlying fetch. A stalled provider socket therefore stays open, the worker's event
|
|
884
|
+
// loop never drains, and the `jeo memory-distill` child lingers forever — one orphan per
|
|
885
|
+
// session, each pinning the full transcript in RSS. That is the observed "jeo bun" pileup
|
|
886
|
+
// (CPU/memory creeping up over time). An explicit exit closes the socket and reclaims it.
|
|
887
|
+
exit: (code: number) => void = (code) => process.exit(code),
|
|
888
|
+
): Promise<void> {
|
|
882
889
|
try {
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
890
|
+
const payloadPath = (args[0] ?? "").trim();
|
|
891
|
+
if (!payloadPath) return;
|
|
892
|
+
try {
|
|
893
|
+
const payloadContent = await fs.readFile(payloadPath, "utf-8");
|
|
894
|
+
const payload = JSON.parse(payloadContent) as { model?: string; messages?: Message[] };
|
|
895
|
+
const bundleDir = path.join(process.cwd(), ".jeo", "memory");
|
|
896
|
+
await saveRawPayload(bundleDir, payload);
|
|
897
|
+
if (Array.isArray(payload.messages)) {
|
|
898
|
+
await distillSessionMemory(payload.messages, process.cwd(), { model: payload.model });
|
|
899
|
+
}
|
|
900
|
+
} catch {
|
|
901
|
+
// best-effort — a broken payload must not leave error noise in a detached child
|
|
902
|
+
} finally {
|
|
903
|
+
await fs.unlink(payloadPath).catch(() => {});
|
|
889
904
|
}
|
|
890
|
-
} catch {
|
|
891
|
-
// best-effort — a broken payload must not leave error noise in a detached child
|
|
892
905
|
} finally {
|
|
893
|
-
|
|
906
|
+
// Guaranteed on every path (incl. the no-payload early return and a distill timeout):
|
|
907
|
+
// never leave the detached worker alive waiting on a dangling fetch.
|
|
908
|
+
exit(0);
|
|
894
909
|
}
|
|
895
910
|
}
|
|
896
911
|
|
|
912
|
+
|
package/src/commands/launch.ts
CHANGED
|
@@ -2650,7 +2650,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
2650
2650
|
setImmediate(() => {
|
|
2651
2651
|
void (async () => {
|
|
2652
2652
|
try {
|
|
2653
|
-
const rli = rl as unknown as { line: string; cursor: number };
|
|
2653
|
+
const rli = rl as unknown as { line: string; cursor: number; _refreshLine?: () => void };
|
|
2654
2654
|
const before = rli.line;
|
|
2655
2655
|
const dropped = await attachImagePaths(before, pendingImages.length + 1);
|
|
2656
2656
|
if (dropped.images.length === 0 || dropped.text === before) return;
|
|
@@ -2658,6 +2658,9 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
2658
2658
|
rli.line = dropped.text;
|
|
2659
2659
|
rli.cursor = dropped.text.length; // drop lands at the caret (line end in the common case)
|
|
2660
2660
|
typedLine = dropped.text;
|
|
2661
|
+
// Keep readline's internal screen model in step with the swapped-in tag so
|
|
2662
|
+
// the caret is not offset on the next keystroke (mirrors the Ctrl+V path).
|
|
2663
|
+
rli._refreshLine?.();
|
|
2661
2664
|
if (previewArmed) drawFooter(previewLines(typedLine, navIdx));
|
|
2662
2665
|
} finally {
|
|
2663
2666
|
pasteInFlight = false;
|
|
@@ -2719,12 +2722,17 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
2719
2722
|
if (!img) return;
|
|
2720
2723
|
pendingImages.push(img);
|
|
2721
2724
|
const tag = `[image #${pendingImages.length}]`;
|
|
2722
|
-
const rli = rl as unknown as { line: string; cursor: number };
|
|
2725
|
+
const rli = rl as unknown as { line: string; cursor: number; _refreshLine?: () => void };
|
|
2723
2726
|
const at = typeof rli.cursor === "number" ? rli.cursor : rli.line.length;
|
|
2724
2727
|
const sep = rli.line.length > 0 && at > 0 && rli.line[at - 1] !== " " ? " " : "";
|
|
2725
2728
|
rli.line = rli.line.slice(0, at) + sep + tag + " " + rli.line.slice(at);
|
|
2726
2729
|
rli.cursor = at + sep.length + tag.length + 1;
|
|
2727
2730
|
typedLine = rli.line;
|
|
2731
|
+
// Sync readline's internal screen model to the injected tag (same as the
|
|
2732
|
+
// slash/tab completion accept paths): without it readline's stale row/cursor
|
|
2733
|
+
// model offsets the real caret several columns on the next keystroke — the
|
|
2734
|
+
// "caret pushed past the tag after attaching an image" shift.
|
|
2735
|
+
rli._refreshLine?.();
|
|
2728
2736
|
if (previewArmed) drawFooter(previewLines(typedLine, navIdx));
|
|
2729
2737
|
} finally {
|
|
2730
2738
|
pasteInFlight = false;
|
package/src/commands/team.ts
CHANGED
|
@@ -346,7 +346,8 @@ export async function runTeamEngine(opts: TeamEngineOptions = {}): Promise<{ ok:
|
|
|
346
346
|
const currentTask = teamState.pending_tasks[0];
|
|
347
347
|
log(`\n${categoryBadge("progress")} Current task: "${currentTask}"`);
|
|
348
348
|
const activeIndex = activeStepIndex(tasks.length, teamState.pending_tasks);
|
|
349
|
-
|
|
349
|
+
// Guide printed once above; per-task lines convey progress. No per-iteration
|
|
350
|
+
// reprint — it was O(N²) lines, climbing the embedded-terminal renderer's CPU.
|
|
350
351
|
|
|
351
352
|
if (opts.onProgress) {
|
|
352
353
|
opts.onProgress({ skill: "team", phase: "executing", detail: `Current task: ${currentTask}` });
|
package/src/tui/app.ts
CHANGED
|
@@ -126,6 +126,15 @@ export function tailForWrap(text: string, maxChars = FRAME_WRAP_TAIL_CHARS): str
|
|
|
126
126
|
return text.length > maxChars ? text.slice(text.length - maxChars) : text;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/** Memoization key for a live-block wrap (Thinking / tool Output). Derived from the
|
|
130
|
+
* TRAILING window only (see `tailForWrap`): the rendered rows depend solely on the
|
|
131
|
+
* tail, so keying on the full streamed buffer just made every 120ms tick and every
|
|
132
|
+
* delta copy + compare the whole growing string — O(len) per frame, i.e. O(len²) over
|
|
133
|
+
* a long stream. Bounding the key to the ≤16KB tail keeps per-frame cost O(window). */
|
|
134
|
+
export function liveBlockWrapKey(wrapW: number, text: string): string {
|
|
135
|
+
return `${wrapW}\u0000${tailForWrap(text)}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
129
138
|
/** Max lines of a committed reasoning block kept in scrollback (gjc-style collapse): a
|
|
130
139
|
* long chain-of-thought is clipped with a "+N more" hint so it never floods the ledger. */
|
|
131
140
|
export const THINKING_COMMIT_MAX_LINES = 12;
|
|
@@ -1300,14 +1309,18 @@ export class LaunchTui {
|
|
|
1300
1309
|
const dim = this.theme.color ? chalk.dim : (s: string) => s;
|
|
1301
1310
|
if (!text.trim()) return [];
|
|
1302
1311
|
const wrapW = Math.max(8, cols - 2);
|
|
1303
|
-
// Memoize the wrap
|
|
1304
|
-
//
|
|
1305
|
-
//
|
|
1306
|
-
//
|
|
1312
|
+
// Memoize the wrap on the TRAILING window only. The block displays just its last few
|
|
1313
|
+
// wrapped rows, so wrap cost is already capped by `tailForWrap`. The cache KEY must be
|
|
1314
|
+
// bounded too: keying on the full (up-to-hundreds-of-KB) `text` made every 120ms tick
|
|
1315
|
+
// and every stream delta copy + compare the whole growing buffer — O(len) per frame, so
|
|
1316
|
+
// O(len²) over a long reasoning/tool stream (the streaming slowdown). Slice first, then
|
|
1317
|
+
// key + wrap the ≤16KB tail: identical visible output, O(tail) per frame regardless of
|
|
1318
|
+
// total size. Per-label slot (Thinking / Output) keyed by wrap width + tail.
|
|
1307
1319
|
let cache = this.liveBlockWrapCaches.get(cacheKey);
|
|
1308
1320
|
if (!cache) { cache = lastValueCache<string[]>(); this.liveBlockWrapCaches.set(cacheKey, cache); }
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1321
|
+
const tail = tailForWrap(text);
|
|
1322
|
+
const wrapped = cache(liveBlockWrapKey(wrapW, text), () =>
|
|
1323
|
+
tail
|
|
1311
1324
|
.split("\n")
|
|
1312
1325
|
.flatMap(l => wrapTextWithAnsi(l, wrapW))
|
|
1313
1326
|
.filter(l => l.length > 0));
|