jeo-code 0.7.30 → 0.7.32
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 +46 -0
- package/README.ja.md +5 -3
- package/README.ko.md +5 -3
- package/README.md +5 -3
- package/README.zh.md +5 -3
- package/package.json +3 -3
- package/scripts/install.sh +19 -0
- package/scripts/uninstall.sh +2 -2
- package/src/agent/AGENTS.md +12 -0
- package/src/agent/ast-edit-tool.ts +123 -0
- package/src/agent/ast-file-scan.ts +62 -0
- package/src/agent/ast-grep-tool.ts +107 -0
- package/src/agent/ast-match.ts +241 -0
- package/src/agent/compaction.ts +6 -19
- package/src/agent/debug-session.ts +435 -0
- package/src/agent/debug-tool.ts +89 -0
- package/src/agent/engine.ts +103 -18
- package/src/agent/goal-tool.ts +57 -0
- package/src/agent/irc-tool.ts +71 -0
- package/src/agent/job-registry.ts +163 -0
- package/src/agent/job-tool.ts +106 -0
- package/src/agent/lsp-rename-tool.ts +108 -0
- package/src/agent/lsp-tool.ts +177 -0
- package/src/agent/memory-graph.ts +6 -1
- package/src/agent/memory.ts +73 -52
- package/src/agent/session.ts +27 -0
- package/src/agent/subagent-registry.ts +40 -4
- package/src/agent/subagent-tool.ts +27 -7
- package/src/agent/subagents.ts +1 -1
- package/src/agent/task-tool.ts +27 -5
- package/src/agent/tools.ts +100 -5
- package/src/agent/ts-language-service.ts +173 -0
- package/src/ai/model-manager.ts +58 -26
- package/src/ai/sse.ts +22 -7
- package/src/ai/think-tags.ts +10 -1
- package/src/commands/deep-interview.ts +49 -1
- package/src/commands/launch/agents-slash.ts +489 -0
- package/src/commands/launch/flags.ts +27 -9
- package/src/commands/launch/model-slash.ts +290 -0
- package/src/commands/launch/session-slash.ts +305 -0
- package/src/commands/launch/slash-handlers.ts +6 -0
- package/src/commands/launch/system-slash.ts +81 -0
- package/src/commands/launch/tmux.ts +32 -0
- package/src/commands/launch.ts +227 -750
- package/src/tui/app.ts +32 -12
- package/src/tui/components/evolution.ts +23 -3
- package/src/tui/components/width.ts +11 -6
package/src/tui/app.ts
CHANGED
|
@@ -288,6 +288,10 @@ export class LaunchTui {
|
|
|
288
288
|
// block above the heartbeat; pressing Ctrl+O again clears it and restores the
|
|
289
289
|
// normal activity view. Kept as data, not scrollback text, so it can actually close.
|
|
290
290
|
private historyLines: string[] | null = null;
|
|
291
|
+
// ponytail: cached wrapped lines for history panel to avoid O(history) re-wrap per tick
|
|
292
|
+
private cachedWrappedHistory: string[] | null = null;
|
|
293
|
+
private cachedHistoryLines: string[] | null = null;
|
|
294
|
+
private cachedHistoryWidth: number | null = null;
|
|
291
295
|
// Ctrl+O detail panel scroll: a window offset so long/CJK content is fully
|
|
292
296
|
// reachable (↑↓/PgUp/PgDn) instead of clipped at "… N more". Bound + page size
|
|
293
297
|
// are recomputed each render from the visible body height.
|
|
@@ -323,6 +327,9 @@ export class LaunchTui {
|
|
|
323
327
|
private readonly unicode: boolean = supportsUnicode();
|
|
324
328
|
// Active color theme (JEO_TUI_THEME), default cosmic; `mono` disables color.
|
|
325
329
|
private readonly theme = resolveTheme(process.env);
|
|
330
|
+
// Terminal color depth, detected once — TERM/COLORTERM/NO_COLOR are process-constant,
|
|
331
|
+
// so re-running the regex chain 2-4× per 120ms draw was pure waste.
|
|
332
|
+
private readonly colorLevel = detectColorLevel(process.env, isTTY());
|
|
326
333
|
// Neon flow palette derived from the active theme (forge-card border + forge mark
|
|
327
334
|
// animation), so the live glow tracks the theme instead of a fixed brand palette.
|
|
328
335
|
private readonly themeFlow = themeFlowPalette(this.theme);
|
|
@@ -557,7 +564,9 @@ export class LaunchTui {
|
|
|
557
564
|
// Sanitize raw child stdout (CR / EL / cursor-move escapes) before it enters the
|
|
558
565
|
// frame — unsanitized control bytes tore the renderer's next \x1b[2K (literal "2K")
|
|
559
566
|
// and hijacked the cursor, corrupting the live frame.
|
|
560
|
-
|
|
567
|
+
// Only the display WINDOW is sanitized: renderLiveBlock tails the text anyway, so
|
|
568
|
+
// scanning the full cumulative buffer per event was O(n²) over a chatty stream.
|
|
569
|
+
this.liveToolOutput = sanitizeForFrame(tailForWrap(partial));
|
|
561
570
|
if (Date.now() - this.lastStreamDraw >= 100) {
|
|
562
571
|
this.lastStreamDraw = Date.now();
|
|
563
572
|
this.draw();
|
|
@@ -1339,10 +1348,18 @@ export class LaunchTui {
|
|
|
1339
1348
|
const inner = Math.max(10, boxWidth - 2);
|
|
1340
1349
|
const accent = this.theme.color ? accentPaint(this.theme) : (s: string) => s;
|
|
1341
1350
|
const dim = this.theme.color ? chalk.dim : (s: string) => s;
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
}
|
|
1351
|
+
let wrapped: string[];
|
|
1352
|
+
if (this.historyLines === this.cachedHistoryLines && inner === this.cachedHistoryWidth && this.cachedWrappedHistory) {
|
|
1353
|
+
wrapped = this.cachedWrappedHistory;
|
|
1354
|
+
} else {
|
|
1355
|
+
wrapped = this.historyLines.flatMap(line => {
|
|
1356
|
+
const physical = line.split("\n");
|
|
1357
|
+
return physical.flatMap(part => (visibleWidth(part) <= inner ? [part] : wrapTextWithAnsi(part, inner)));
|
|
1358
|
+
});
|
|
1359
|
+
this.cachedHistoryLines = this.historyLines;
|
|
1360
|
+
this.cachedHistoryWidth = inner;
|
|
1361
|
+
this.cachedWrappedHistory = wrapped;
|
|
1362
|
+
}
|
|
1346
1363
|
const bodyLimit = Math.max(1, maxRows - 4); // box borders (2) + title + divider
|
|
1347
1364
|
const scrollable = wrapped.length > bodyLimit;
|
|
1348
1365
|
// Window capacity at the bottom (worst case: both ↑ and ↓ indicators take a row),
|
|
@@ -1433,7 +1450,7 @@ export class LaunchTui {
|
|
|
1433
1450
|
}): string[] {
|
|
1434
1451
|
const { cols, rows, stepNow, elapsedMs, idx, isThinking } = args;
|
|
1435
1452
|
const dim = this.theme.color ? chalk.dim : (s: string) => s;
|
|
1436
|
-
const colorLevel =
|
|
1453
|
+
const colorLevel = this.colorLevel;
|
|
1437
1454
|
// One quantized animation clock for the whole frame: gradient phase cycles 20
|
|
1438
1455
|
// steps, the prompt beat advances every 3 ticks. Quantization keeps repaints
|
|
1439
1456
|
// coherent (status field + forge border move together) and bounds per-tick work.
|
|
@@ -1541,7 +1558,7 @@ export class LaunchTui {
|
|
|
1541
1558
|
cols,
|
|
1542
1559
|
unicode: this.unicode,
|
|
1543
1560
|
color: this.theme.color,
|
|
1544
|
-
colorLevel:
|
|
1561
|
+
colorLevel: this.colorLevel,
|
|
1545
1562
|
gradient: themeGradient(this.theme, 2),
|
|
1546
1563
|
});
|
|
1547
1564
|
}
|
|
@@ -1584,7 +1601,7 @@ export class LaunchTui {
|
|
|
1584
1601
|
frame: twist,
|
|
1585
1602
|
unicode: this.unicode,
|
|
1586
1603
|
color: this.theme.color,
|
|
1587
|
-
colorLevel:
|
|
1604
|
+
colorLevel: this.colorLevel,
|
|
1588
1605
|
palette: this.themeFlow,
|
|
1589
1606
|
});
|
|
1590
1607
|
this.cachedArt = fit ? centerBlock(art, innerWidth) : art;
|
|
@@ -1598,9 +1615,6 @@ export class LaunchTui {
|
|
|
1598
1615
|
const trackCount = showArt ? 1 : 0;
|
|
1599
1616
|
const headerHeight = artLinesCount + trackCount + (showArt ? 1 : 0);
|
|
1600
1617
|
|
|
1601
|
-
const toolLines = this.tools.render(fit ? Math.max(3, rows - 15) : undefined, { color: this.theme.color, indexed: fit });
|
|
1602
|
-
const toolListHeight = toolLines.length;
|
|
1603
|
-
|
|
1604
1618
|
const planLines = this.renderPlan(this.theme.color);
|
|
1605
1619
|
const planHeight = planLines.length;
|
|
1606
1620
|
|
|
@@ -1619,6 +1633,12 @@ export class LaunchTui {
|
|
|
1619
1633
|
return;
|
|
1620
1634
|
}
|
|
1621
1635
|
|
|
1636
|
+
// Tool rows are only consumed by the boxed (non-inline) layout below — computing
|
|
1637
|
+
// them before the inline early-return re-painted up to 500 chalk rows per tick
|
|
1638
|
+
// on the DEFAULT path that never uses them.
|
|
1639
|
+
const toolLines = this.tools.render(fit ? Math.max(3, rows - 15) : undefined, { color: this.theme.color, indexed: fit });
|
|
1640
|
+
const toolListHeight = toolLines.length;
|
|
1641
|
+
|
|
1622
1642
|
// Bottom-pinned status + footer.
|
|
1623
1643
|
const bottom: string[] = [];
|
|
1624
1644
|
const statusMsg = this.currentActivity();
|
|
@@ -1626,7 +1646,7 @@ export class LaunchTui {
|
|
|
1626
1646
|
// known price — then no $ is shown). Computed once per draw, fed to status + footer.
|
|
1627
1647
|
const costUsd = costForUsage(this.footer.model, this.turnUsage) ?? undefined;
|
|
1628
1648
|
if (isThinking) {
|
|
1629
|
-
const colorLevel =
|
|
1649
|
+
const colorLevel = this.colorLevel;
|
|
1630
1650
|
const phase = (this.tickCount * 0.05) % 1;
|
|
1631
1651
|
const grad = themeGradient(this.theme, idx);
|
|
1632
1652
|
const palette = [grad.from, grad.to];
|
|
@@ -308,7 +308,17 @@ export function transitionMessage(index: number): string {
|
|
|
308
308
|
* Applies a dimensional, animated gradient (foreground or background) to a string of text.
|
|
309
309
|
* Driven by a phaseMs timestamp, it creates a slow, tasteful shimmer wave
|
|
310
310
|
* and adds depth cues (bright core, dimmer edges).
|
|
311
|
+
*
|
|
312
|
+
* The clock is QUANTIZED to 225ms buckets (20 discrete phases over the 4.5s period,
|
|
313
|
+
* matching the forge art's quantized-clock rule): callers pass raw Date.now(), but two
|
|
314
|
+
* calls in the same bucket produce byte-identical output, so the differential renderer
|
|
315
|
+
* skips the row instead of rewriting ~2-4KB of truecolor escapes on every idle tick.
|
|
316
|
+
* A small memo returns the exact prior string for a repeated (bucket, style, text) —
|
|
317
|
+
* the common case between consecutive 120ms ticks.
|
|
311
318
|
*/
|
|
319
|
+
const GRADIENT_BUCKET_MS = 225;
|
|
320
|
+
const gradientMemo = new Map<string, string>();
|
|
321
|
+
const GRADIENT_MEMO_CAP = 16;
|
|
312
322
|
export function applyDimensionalGradient(
|
|
313
323
|
text: string,
|
|
314
324
|
timeMs: number,
|
|
@@ -320,6 +330,10 @@ export function applyDimensionalGradient(
|
|
|
320
330
|
): string {
|
|
321
331
|
const plain = stripAnsi(text);
|
|
322
332
|
if (level === ColorLevel.None || plain.length === 0) return plain;
|
|
333
|
+
const bucket = timeMs - (timeMs % GRADIENT_BUCKET_MS);
|
|
334
|
+
const memoKey = `${bucket}|${fromHex}|${toHex}|${level}|${isBg ? 1 : 0}|${fgHex}|${plain}`;
|
|
335
|
+
const hit = gradientMemo.get(memoKey);
|
|
336
|
+
if (hit !== undefined) return hit;
|
|
323
337
|
|
|
324
338
|
const from = hexToRgb(fromHex);
|
|
325
339
|
const to = hexToRgb(toHex);
|
|
@@ -331,8 +345,8 @@ export function applyDimensionalGradient(
|
|
|
331
345
|
out += fgEscape(fg, level);
|
|
332
346
|
}
|
|
333
347
|
|
|
334
|
-
// Slow period: 4.5 seconds for a complete wave cycle
|
|
335
|
-
const p = (
|
|
348
|
+
// Slow period: 4.5 seconds for a complete wave cycle (quantized bucket → 20 phases)
|
|
349
|
+
const p = (bucket / 4500) % 1;
|
|
336
350
|
|
|
337
351
|
for (let i = 0; i < L; i++) {
|
|
338
352
|
const ch = plain[i]!;
|
|
@@ -380,5 +394,11 @@ export function applyDimensionalGradient(
|
|
|
380
394
|
}
|
|
381
395
|
}
|
|
382
396
|
|
|
383
|
-
|
|
397
|
+
const result = out + resetEscape(level);
|
|
398
|
+
if (gradientMemo.size >= GRADIENT_MEMO_CAP) {
|
|
399
|
+
const oldest = gradientMemo.keys().next().value;
|
|
400
|
+
if (oldest !== undefined) gradientMemo.delete(oldest);
|
|
401
|
+
}
|
|
402
|
+
gradientMemo.set(memoKey, result);
|
|
403
|
+
return result;
|
|
384
404
|
}
|
|
@@ -217,9 +217,13 @@ export function wrapTextWithAnsi(text: string, cols: number): string[] {
|
|
|
217
217
|
out.push(rawLine);
|
|
218
218
|
continue;
|
|
219
219
|
}
|
|
220
|
+
if (!rawLine.includes("\x1b") && !/[\t\u0300-\uffff]/.test(rawLine) && !/[\u{10000}-\u{10ffff}]/u.test(rawLine)) {
|
|
221
|
+
for (let i = 0; i < rawLine.length; i += width) out.push(rawLine.slice(i, i + width));
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
220
224
|
let rest = rawLine;
|
|
221
225
|
let active = ""; // SGR open at the wrap boundary, carried to the next line
|
|
222
|
-
while (
|
|
226
|
+
while (true) {
|
|
223
227
|
const head = truncateToWidth(rest, width);
|
|
224
228
|
// Advance past exactly the consumed substring. truncateToWidth may append a
|
|
225
229
|
// SYNTHETIC trailing reset (frame safety) that is NOT in `rest` — including it
|
|
@@ -230,6 +234,12 @@ export function wrapTextWithAnsi(text: string, cols: number): string[] {
|
|
|
230
234
|
: head.endsWith(RESET) && rest.startsWith(head.slice(0, -RESET.length))
|
|
231
235
|
? head.slice(0, -RESET.length)
|
|
232
236
|
: head;
|
|
237
|
+
if (consumed.length === rest.length) {
|
|
238
|
+
let line = active + rest;
|
|
239
|
+
if (active && !line.endsWith(RESET)) line += RESET;
|
|
240
|
+
out.push(line);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
233
243
|
let line = active + head;
|
|
234
244
|
const next = sgrStateAfter(active, consumed);
|
|
235
245
|
// Close any color still open at the line end so it cannot tint the padding/border.
|
|
@@ -238,11 +248,6 @@ export function wrapTextWithAnsi(text: string, cols: number): string[] {
|
|
|
238
248
|
active = next;
|
|
239
249
|
rest = rest.slice(consumed.length);
|
|
240
250
|
}
|
|
241
|
-
if (rest.length > 0) {
|
|
242
|
-
let line = active + rest;
|
|
243
|
-
if (active && !line.endsWith(RESET)) line += RESET;
|
|
244
|
-
out.push(line);
|
|
245
|
-
}
|
|
246
251
|
}
|
|
247
252
|
return out;
|
|
248
253
|
}
|