throughline 0.3.6 → 0.3.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/package.json +1 -1
- package/src/token-monitor.mjs +31 -18
- package/src/token-monitor.test.mjs +41 -35
package/package.json
CHANGED
package/src/token-monitor.mjs
CHANGED
|
@@ -235,21 +235,27 @@ export function shouldForceFullRedraw(prevCols, currCols) {
|
|
|
235
235
|
* 描画に使う列幅を解決する。
|
|
236
236
|
*
|
|
237
237
|
* 優先順:
|
|
238
|
-
* 1. `process.stdout.columns` が 40 以上
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* 返すことがあり、そのまま使うと描画が「openclaw」で切れるなどの視覚崩れが出る)
|
|
238
|
+
* 1. **stdout が TTY** かつ `process.stdout.columns` が 40 以上
|
|
239
|
+
* → その値から 1 引いたもの(末尾列での自動改行回避)
|
|
240
|
+
* 2. `process.env.COLUMNS` が 40 以上 → その値 - 1
|
|
241
|
+
* 3. それ以外 → 200 にフォールバック
|
|
243
242
|
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
243
|
+
* 非 TTY のとき columns を信用しない理由:
|
|
244
|
+
* VSCode の `type: process` タスクは stdout を PTY ではなくパイプとして渡すため、
|
|
245
|
+
* - 起動時の columns が undefined / 0 / 12 のような極端な値になることがある
|
|
246
|
+
* - ターミナル panel をドラッグで広げても SIGWINCH が届かず columns が更新されない
|
|
247
|
+
* - 結果として起動時に狭かった幅のまま永久に truncate され続ける
|
|
248
|
+
* `isTTY` が false の時点で columns 値は信頼できない契約だと割り切り、env.COLUMNS か
|
|
249
|
+
* 固定 200 にフォールバックする。
|
|
246
250
|
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
251
|
+
* 200 固定フォールバックは、truncateToCells が 200 セル以下の実内容をそのまま通す
|
|
252
|
+
* (伸長しない)ので過大でも副作用なし。
|
|
249
253
|
*/
|
|
250
254
|
export function resolveColumns() {
|
|
251
|
-
|
|
252
|
-
|
|
255
|
+
if (process.stdout.isTTY) {
|
|
256
|
+
const reported = typeof process.stdout.columns === 'number' ? process.stdout.columns : 0;
|
|
257
|
+
if (reported >= 40) return reported - 1;
|
|
258
|
+
}
|
|
253
259
|
const fromEnv = Number(process.env.COLUMNS);
|
|
254
260
|
if (Number.isFinite(fromEnv) && fromEnv >= 40) return fromEnv - 1;
|
|
255
261
|
return 200;
|
|
@@ -414,13 +420,20 @@ function renderFrame(args) {
|
|
|
414
420
|
const columns = resolveColumns();
|
|
415
421
|
const clipped = lines.map((l) => truncateToCells(l, columns));
|
|
416
422
|
|
|
417
|
-
//
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
423
|
+
// 再描画戦略:
|
|
424
|
+
// - TTY: 真の columns が分かるので CUU + clearBelow で部分再描画(省フリッカ)
|
|
425
|
+
// - 非 TTY (VSCode の type:process タスク等): columns を信用できないので 200 で
|
|
426
|
+
// truncate しているが、実ターミナル幅が 200 未満なら自動改行が起き、論理行数と
|
|
427
|
+
// 物理行数がズレて CUU が誤作動する(「1 セッション」行が毎フレーム積み上がる
|
|
428
|
+
// バグを実機で確認)。非 TTY では画面全クリアで愚直に描き直す方が確実。
|
|
429
|
+
// どちらも差分検知 (needsRerender) を通過したフレームのみ書き込むので、
|
|
430
|
+
// フリッカ量はデータ変化頻度に比例するだけで爆発しない。
|
|
431
|
+
if (process.stdout.isTTY) {
|
|
432
|
+
if (lastRenderedLines > 0) {
|
|
433
|
+
process.stdout.write(ANSI.up(lastRenderedLines) + '\r' + ANSI.clearBelow);
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
process.stdout.write(ANSI.clearScreen);
|
|
424
437
|
}
|
|
425
438
|
|
|
426
439
|
process.stdout.write(clipped.join('\n') + '\n');
|
|
@@ -388,54 +388,60 @@ test('shouldForceFullRedraw: 片方 0 以下は false (未初期化 or 無効)',
|
|
|
388
388
|
|
|
389
389
|
// ─── resolveColumns ───────────────────────────────────────────────
|
|
390
390
|
|
|
391
|
-
|
|
392
|
-
const
|
|
391
|
+
function withStdoutState({ isTTY, columns, envColumns }, fn) {
|
|
392
|
+
const origIsTTY = process.stdout.isTTY;
|
|
393
|
+
const origColumns = process.stdout.columns;
|
|
394
|
+
const origEnv = process.env.COLUMNS;
|
|
393
395
|
try {
|
|
394
|
-
Object.defineProperty(process.stdout, '
|
|
395
|
-
|
|
396
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: isTTY, configurable: true, writable: true });
|
|
397
|
+
Object.defineProperty(process.stdout, 'columns', { value: columns, configurable: true, writable: true });
|
|
398
|
+
if (envColumns === undefined) delete process.env.COLUMNS;
|
|
399
|
+
else process.env.COLUMNS = envColumns;
|
|
400
|
+
return fn();
|
|
396
401
|
} finally {
|
|
397
|
-
Object.defineProperty(process.stdout, '
|
|
402
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: origIsTTY, configurable: true, writable: true });
|
|
403
|
+
Object.defineProperty(process.stdout, 'columns', { value: origColumns, configurable: true, writable: true });
|
|
404
|
+
if (origEnv === undefined) delete process.env.COLUMNS;
|
|
405
|
+
else process.env.COLUMNS = origEnv;
|
|
398
406
|
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
test('resolveColumns: TTY かつ columns が 40 以上 → その値 - 1', () => {
|
|
410
|
+
withStdoutState({ isTTY: true, columns: 120, envColumns: undefined }, () => {
|
|
411
|
+
assert.equal(resolveColumns(), 119);
|
|
412
|
+
});
|
|
399
413
|
});
|
|
400
414
|
|
|
401
|
-
test('resolveColumns: columns
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
Object.defineProperty(process.stdout, 'columns', { value: 12, configurable: true, writable: true });
|
|
406
|
-
delete process.env.COLUMNS;
|
|
415
|
+
test('resolveColumns: 非 TTY なら columns が大きくても信用しない (env も 200 も使う)', () => {
|
|
416
|
+
// type: process タスクで columns が 120 にセットされたが実際の幅とは連動しない、という状況。
|
|
417
|
+
// env.COLUMNS も無ければ 200 フォールバック。
|
|
418
|
+
withStdoutState({ isTTY: false, columns: 120, envColumns: undefined }, () => {
|
|
407
419
|
assert.equal(resolveColumns(), 200);
|
|
408
|
-
}
|
|
409
|
-
Object.defineProperty(process.stdout, 'columns', { value: orig, configurable: true, writable: true });
|
|
410
|
-
if (origEnv !== undefined) process.env.COLUMNS = origEnv;
|
|
411
|
-
}
|
|
420
|
+
});
|
|
412
421
|
});
|
|
413
422
|
|
|
414
|
-
test('resolveColumns:
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
423
|
+
test('resolveColumns: TTY でも columns が小さすぎる値 (12 等) ならフォールバック 200', () => {
|
|
424
|
+
withStdoutState({ isTTY: true, columns: 12, envColumns: undefined }, () => {
|
|
425
|
+
assert.equal(resolveColumns(), 200);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('resolveColumns: 非 TTY でも env.COLUMNS >= 40 があればそれを使う', () => {
|
|
430
|
+
withStdoutState({ isTTY: false, columns: undefined, envColumns: '150' }, () => {
|
|
420
431
|
assert.equal(resolveColumns(), 149);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test('resolveColumns: TTY で columns 未設定、env も無ければフォールバック 200', () => {
|
|
436
|
+
withStdoutState({ isTTY: true, columns: undefined, envColumns: undefined }, () => {
|
|
437
|
+
assert.equal(resolveColumns(), 200);
|
|
438
|
+
});
|
|
426
439
|
});
|
|
427
440
|
|
|
428
441
|
test('resolveColumns: 全てのソースが無効ならフォールバック 200', () => {
|
|
429
|
-
|
|
430
|
-
const origEnv = process.env.COLUMNS;
|
|
431
|
-
try {
|
|
432
|
-
Object.defineProperty(process.stdout, 'columns', { value: undefined, configurable: true, writable: true });
|
|
433
|
-
delete process.env.COLUMNS;
|
|
442
|
+
withStdoutState({ isTTY: false, columns: undefined, envColumns: undefined }, () => {
|
|
434
443
|
assert.equal(resolveColumns(), 200);
|
|
435
|
-
}
|
|
436
|
-
Object.defineProperty(process.stdout, 'columns', { value: orig, configurable: true, writable: true });
|
|
437
|
-
if (origEnv !== undefined) process.env.COLUMNS = origEnv;
|
|
438
|
-
}
|
|
444
|
+
});
|
|
439
445
|
});
|
|
440
446
|
|
|
441
447
|
// ─── formatLine: time-ago 表示 ────────────────────────────────────
|