throughline 0.3.11 → 0.3.13
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 +60 -51
- package/src/token-monitor.test.mjs +19 -14
package/package.json
CHANGED
package/src/token-monitor.mjs
CHANGED
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
* - トークン数は transcript JSONL の最新 assistant usage を直読
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { basename } from 'node:path';
|
|
20
|
+
import { basename, dirname, join } from 'node:path';
|
|
21
21
|
import { stripVTControlCharacters } from 'node:util';
|
|
22
|
-
import { statSync, existsSync } from 'node:fs';
|
|
22
|
+
import { statSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
23
|
+
import { homedir } from 'node:os';
|
|
23
24
|
import { getStateDir, readAllSessionStates, snapshotStateMtimes, normalizeProjectPath } from './state-file.mjs';
|
|
24
25
|
import { readLatestUsage } from './transcript-usage.mjs';
|
|
25
26
|
|
|
@@ -241,30 +242,29 @@ export function shouldForceFullRedraw(prevCols, currCols) {
|
|
|
241
242
|
* 描画に使う列幅を解決する。
|
|
242
243
|
*
|
|
243
244
|
* 優先順:
|
|
244
|
-
* 1. **stdout が TTY** かつ `process.stdout.columns` が
|
|
245
|
+
* 1. **stdout が TTY** かつ `process.stdout.columns` が 1 以上
|
|
245
246
|
* → その値から 1 引いたもの(末尾列での自動改行回避)
|
|
246
|
-
* 2. `process.env.COLUMNS` が
|
|
247
|
-
* 3. それ以外 →
|
|
247
|
+
* 2. `process.env.COLUMNS` が 1 以上 → その値 - 1
|
|
248
|
+
* 3. それ以外 → 80 にフォールバック
|
|
248
249
|
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* `isTTY` が false の時点で columns 値は信頼できない契約だと割り切り、env.COLUMNS か
|
|
255
|
-
* 固定 200 にフォールバックする。
|
|
250
|
+
* 歴史: 0.3.6〜0.3.12 までは `>= 40` の閾値を設けて「狂った小さい値は捨てる」挙動
|
|
251
|
+
* だった。しかし実際の VSCode task panel は 30 cells 程度で起動することがあり (実測)、
|
|
252
|
+
* 真の columns=30 なのに閾値に引っかかって 200 フォールバックに倒れ、30 cell 端末に
|
|
253
|
+
* 200 cell の行を書いて 7 行に折り返し、`\x1b[NA` が 7 倍 under-count して
|
|
254
|
+
* 描画が永遠に積み上がるバグの真因だった。
|
|
256
255
|
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
256
|
+
* 正の columns はすべて信頼する。真の幅に合わせて truncate すれば物理行 = 論理行
|
|
257
|
+
* が保証され、CUU 戻り先が正確になる。狭い terminal ではコンテンツが切り詰められるが、
|
|
258
|
+
* 積み上がりに比べれば遥かにマシ (ユーザーが panel を広げれば full UI が見える)。
|
|
259
259
|
*/
|
|
260
260
|
export function resolveColumns() {
|
|
261
261
|
if (process.stdout.isTTY) {
|
|
262
262
|
const reported = typeof process.stdout.columns === 'number' ? process.stdout.columns : 0;
|
|
263
|
-
if (reported
|
|
263
|
+
if (reported > 0) return Math.max(1, reported - 1);
|
|
264
264
|
}
|
|
265
265
|
const fromEnv = Number(process.env.COLUMNS);
|
|
266
|
-
if (Number.isFinite(fromEnv) && fromEnv
|
|
267
|
-
return
|
|
266
|
+
if (Number.isFinite(fromEnv) && fromEnv > 0) return Math.max(1, fromEnv - 1);
|
|
267
|
+
return 80;
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
function formatLine({ state, usage, isActive, now = Date.now() }) {
|
|
@@ -476,12 +476,22 @@ function safeRenderFrame(args) {
|
|
|
476
476
|
* 変えてきたが、PTY が張られるかどうかは VSCode のバージョンや Windows の ConPTY
|
|
477
477
|
* 実装に依存し、推測は外れ続けた。このコマンドで実測値を 1 ページに出すことで、
|
|
478
478
|
* 「この環境では何が起きているか」を断定できるようにする。
|
|
479
|
+
*
|
|
480
|
+
* 出力は ~/.throughline/last-diag.txt にも保存する。task terminal のようにスクロール
|
|
481
|
+
* バックが分かりづらい環境でも後からファイルを読めば全情報を回収できる。
|
|
479
482
|
*/
|
|
480
483
|
function runDiagnostic() {
|
|
481
|
-
const
|
|
482
|
-
|
|
484
|
+
const lines = [];
|
|
485
|
+
const emit = (s) => {
|
|
486
|
+
process.stdout.write(s);
|
|
487
|
+
lines.push(s);
|
|
488
|
+
};
|
|
489
|
+
const out = (k, v) => emit(`${k.padEnd(28)}${v}\n`);
|
|
490
|
+
|
|
491
|
+
emit('=== Throughline monitor diagnostic ===\n');
|
|
492
|
+
emit(`timestamp: ${new Date().toISOString()}\n\n`);
|
|
483
493
|
|
|
484
|
-
|
|
494
|
+
emit('[process.stdout]\n');
|
|
485
495
|
out(' isTTY', String(Boolean(process.stdout.isTTY)));
|
|
486
496
|
out(' columns', String(process.stdout.columns ?? '(undefined)'));
|
|
487
497
|
out(' rows', String(process.stdout.rows ?? '(undefined)'));
|
|
@@ -490,46 +500,45 @@ function runDiagnostic() {
|
|
|
490
500
|
? String(process.stdout.hasColors())
|
|
491
501
|
: '(n/a)',
|
|
492
502
|
);
|
|
493
|
-
|
|
503
|
+
emit('\n');
|
|
494
504
|
|
|
495
|
-
|
|
505
|
+
emit('[process.stderr]\n');
|
|
496
506
|
out(' isTTY', String(Boolean(process.stderr.isTTY)));
|
|
497
|
-
|
|
507
|
+
emit('\n');
|
|
498
508
|
|
|
499
|
-
|
|
500
|
-
for (const key of ['TERM', 'TERM_PROGRAM', 'TERM_PROGRAM_VERSION', 'COLUMNS', 'LINES', 'VSCODE_PID', 'VSCODE_IPC_HOOK_CLI', 'VSCODE_INJECTION', 'WT_SESSION', 'ConEmuPID']) {
|
|
509
|
+
emit('[env]\n');
|
|
510
|
+
for (const key of ['TERM', 'TERM_PROGRAM', 'TERM_PROGRAM_VERSION', 'COLORTERM', 'COLUMNS', 'LINES', 'VSCODE_PID', 'VSCODE_IPC_HOOK_CLI', 'VSCODE_INJECTION', 'VSCODE_SHELL_INTEGRATION', 'WT_SESSION', 'ConEmuPID', 'OSTYPE', 'MSYSTEM']) {
|
|
501
511
|
out(` ${key}`, process.env[key] ?? '(unset)');
|
|
502
512
|
}
|
|
503
|
-
|
|
513
|
+
emit('\n');
|
|
504
514
|
|
|
505
|
-
|
|
515
|
+
emit('[resolveColumns()]\n');
|
|
506
516
|
out(' value', String(resolveColumns()));
|
|
507
|
-
|
|
517
|
+
emit('\n');
|
|
508
518
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
process.
|
|
512
|
-
|
|
513
|
-
process.stdout.write(' スクリーンショットを取って、フレーム A/B/C が「積み上がり」か「上書き」か教えてください。\n\n');
|
|
519
|
+
emit('[platform]\n');
|
|
520
|
+
out(' process.platform', process.platform);
|
|
521
|
+
out(' process.versions.node', process.versions.node);
|
|
522
|
+
emit('\n');
|
|
514
523
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
524
|
+
emit('=== end of diag ===\n');
|
|
525
|
+
emit('\nこの出力を全文コピーするか、~/.throughline/last-diag.txt を開いて内容を共有してください。\n');
|
|
526
|
+
emit('task terminal でスクロールして読めない場合は PowerShell で\n');
|
|
527
|
+
emit(' cat ~/.throughline/last-diag.txt\n');
|
|
528
|
+
emit('を実行すれば同じ内容が読めます。\n');
|
|
529
|
+
|
|
530
|
+
// ログファイルに保存 (task terminal のようにスクロール不能な環境向け)
|
|
531
|
+
try {
|
|
532
|
+
const logPath = join(homedir(), '.throughline', 'last-diag.txt');
|
|
533
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
534
|
+
writeFileSync(logPath, lines.join(''));
|
|
535
|
+
emit(`\n[ログ保存] ${logPath}\n`);
|
|
536
|
+
} catch (err) {
|
|
537
|
+
const msg = err instanceof Error ? err.message : 'unknown';
|
|
538
|
+
emit(`\n[ログ保存失敗] ${msg}\n`);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
process.exit(0);
|
|
533
542
|
}
|
|
534
543
|
|
|
535
544
|
export function main() {
|
|
@@ -410,41 +410,46 @@ function withStdoutState({ isTTY, columns, envColumns }, fn) {
|
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
-
test('resolveColumns: TTY かつ columns
|
|
413
|
+
test('resolveColumns: TTY かつ columns が正値 → その値 - 1', () => {
|
|
414
414
|
withStdoutState({ isTTY: true, columns: 120, envColumns: undefined }, () => {
|
|
415
415
|
assert.equal(resolveColumns(), 119);
|
|
416
416
|
});
|
|
417
417
|
});
|
|
418
418
|
|
|
419
|
-
test('resolveColumns:
|
|
420
|
-
//
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
assert.equal(resolveColumns(), 200);
|
|
419
|
+
test('resolveColumns: TTY かつ columns が狭い値 (VSCode task panel 等の実値) も信用する', () => {
|
|
420
|
+
// 0.3.6〜0.3.12 の閾値 40 バグを再現防止: 30 cells は実測ベースで正当な値
|
|
421
|
+
withStdoutState({ isTTY: true, columns: 30, envColumns: undefined }, () => {
|
|
422
|
+
assert.equal(resolveColumns(), 29);
|
|
424
423
|
});
|
|
425
424
|
});
|
|
426
425
|
|
|
427
|
-
test('resolveColumns: TTY
|
|
428
|
-
withStdoutState({ isTTY: true, columns:
|
|
429
|
-
assert.equal(resolveColumns(),
|
|
426
|
+
test('resolveColumns: TTY かつ columns=1 → 1 にクランプ (columns-1=0 回避)', () => {
|
|
427
|
+
withStdoutState({ isTTY: true, columns: 1, envColumns: undefined }, () => {
|
|
428
|
+
assert.equal(resolveColumns(), 1);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test('resolveColumns: 非 TTY は columns を信用しない', () => {
|
|
433
|
+
withStdoutState({ isTTY: false, columns: 120, envColumns: undefined }, () => {
|
|
434
|
+
assert.equal(resolveColumns(), 80);
|
|
430
435
|
});
|
|
431
436
|
});
|
|
432
437
|
|
|
433
|
-
test('resolveColumns: 非 TTY でも env.COLUMNS
|
|
438
|
+
test('resolveColumns: 非 TTY でも env.COLUMNS があればそれを使う', () => {
|
|
434
439
|
withStdoutState({ isTTY: false, columns: undefined, envColumns: '150' }, () => {
|
|
435
440
|
assert.equal(resolveColumns(), 149);
|
|
436
441
|
});
|
|
437
442
|
});
|
|
438
443
|
|
|
439
|
-
test('resolveColumns: TTY で columns 未設定、env も無ければフォールバック
|
|
444
|
+
test('resolveColumns: TTY で columns 未設定、env も無ければフォールバック 80', () => {
|
|
440
445
|
withStdoutState({ isTTY: true, columns: undefined, envColumns: undefined }, () => {
|
|
441
|
-
assert.equal(resolveColumns(),
|
|
446
|
+
assert.equal(resolveColumns(), 80);
|
|
442
447
|
});
|
|
443
448
|
});
|
|
444
449
|
|
|
445
|
-
test('resolveColumns: 全てのソースが無効ならフォールバック
|
|
450
|
+
test('resolveColumns: 全てのソースが無効ならフォールバック 80', () => {
|
|
446
451
|
withStdoutState({ isTTY: false, columns: undefined, envColumns: undefined }, () => {
|
|
447
|
-
assert.equal(resolveColumns(),
|
|
452
|
+
assert.equal(resolveColumns(), 80);
|
|
448
453
|
});
|
|
449
454
|
});
|
|
450
455
|
|