throughline 0.3.10 → 0.3.11
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 +78 -3
- package/src/token-monitor.test.mjs +9 -5
package/package.json
CHANGED
package/src/token-monitor.mjs
CHANGED
|
@@ -34,7 +34,11 @@ const ANSI = {
|
|
|
34
34
|
hideCursor: '\x1b[?25l',
|
|
35
35
|
showCursor: '\x1b[?25h',
|
|
36
36
|
clearLine: '\x1b[2K',
|
|
37
|
-
|
|
37
|
+
// xterm.js は `\x1b[2J` (ED2) をビューポート消去としてしか実装しておらず、
|
|
38
|
+
// 消えた内容はスクロールバックに残る(xterm.js issue #5019 の jerch コメント参照)。
|
|
39
|
+
// VSCode task panel でフレーム更新が「積み上がる」ように見えるのはこれが原因。
|
|
40
|
+
// `\x1b[3J` (ED3) を続けて送るとスクロールバックも消えて真の全クリアになる。
|
|
41
|
+
clearScreen: '\x1b[2J\x1b[3J\x1b[H',
|
|
38
42
|
clearBelow: '\x1b[0J', // 現在位置から画面末尾までをクリア
|
|
39
43
|
up: (n) => `\x1b[${n}A`, // CUU: カーソルを N 行上へ (列は変えない)
|
|
40
44
|
reset: '\x1b[0m',
|
|
@@ -162,14 +166,16 @@ function padCellsEnd(s, targetCells) {
|
|
|
162
166
|
// --- CLI 引数 ---
|
|
163
167
|
/**
|
|
164
168
|
* @param {string[]} argv
|
|
165
|
-
* @returns {{all: boolean, session: string|null}}
|
|
169
|
+
* @returns {{all: boolean, session: string|null, diag: boolean}}
|
|
166
170
|
* @throws {Error} --session に値が欠落している場合
|
|
167
171
|
*/
|
|
168
172
|
function parseArgs(argv) {
|
|
169
|
-
const args = { all: false, session: null };
|
|
173
|
+
const args = { all: false, session: null, diag: false };
|
|
170
174
|
for (let i = 0; i < argv.length; i++) {
|
|
171
175
|
if (argv[i] === '--all') {
|
|
172
176
|
args.all = true;
|
|
177
|
+
} else if (argv[i] === '--diag') {
|
|
178
|
+
args.diag = true;
|
|
173
179
|
} else if (argv[i] === '--session') {
|
|
174
180
|
const value = argv[i + 1];
|
|
175
181
|
if (value === undefined || value.startsWith('--')) {
|
|
@@ -462,6 +468,70 @@ function safeRenderFrame(args) {
|
|
|
462
468
|
}
|
|
463
469
|
}
|
|
464
470
|
|
|
471
|
+
/**
|
|
472
|
+
* 環境診断モード。ユーザー環境で「モニターの描画が壊れる」と報告されたときに、
|
|
473
|
+
* 実際の process.stdout / env / TERM の値をその場で可視化するための一発起動モード。
|
|
474
|
+
*
|
|
475
|
+
* 過去に「`type: process` だと非 TTY」「`type: shell` なら PTY」など推測で描画戦略を
|
|
476
|
+
* 変えてきたが、PTY が張られるかどうかは VSCode のバージョンや Windows の ConPTY
|
|
477
|
+
* 実装に依存し、推測は外れ続けた。このコマンドで実測値を 1 ページに出すことで、
|
|
478
|
+
* 「この環境では何が起きているか」を断定できるようにする。
|
|
479
|
+
*/
|
|
480
|
+
function runDiagnostic() {
|
|
481
|
+
const out = (k, v) => process.stdout.write(`${k.padEnd(28)}${v}\n`);
|
|
482
|
+
process.stdout.write('=== Throughline monitor diagnostic ===\n\n');
|
|
483
|
+
|
|
484
|
+
process.stdout.write('[process.stdout]\n');
|
|
485
|
+
out(' isTTY', String(Boolean(process.stdout.isTTY)));
|
|
486
|
+
out(' columns', String(process.stdout.columns ?? '(undefined)'));
|
|
487
|
+
out(' rows', String(process.stdout.rows ?? '(undefined)'));
|
|
488
|
+
out(' hasColors()',
|
|
489
|
+
typeof process.stdout.hasColors === 'function'
|
|
490
|
+
? String(process.stdout.hasColors())
|
|
491
|
+
: '(n/a)',
|
|
492
|
+
);
|
|
493
|
+
process.stdout.write('\n');
|
|
494
|
+
|
|
495
|
+
process.stdout.write('[process.stderr]\n');
|
|
496
|
+
out(' isTTY', String(Boolean(process.stderr.isTTY)));
|
|
497
|
+
process.stdout.write('\n');
|
|
498
|
+
|
|
499
|
+
process.stdout.write('[env]\n');
|
|
500
|
+
for (const key of ['TERM', 'TERM_PROGRAM', 'TERM_PROGRAM_VERSION', 'COLUMNS', 'LINES', 'VSCODE_PID', 'VSCODE_IPC_HOOK_CLI', 'VSCODE_INJECTION', 'WT_SESSION', 'ConEmuPID']) {
|
|
501
|
+
out(` ${key}`, process.env[key] ?? '(unset)');
|
|
502
|
+
}
|
|
503
|
+
process.stdout.write('\n');
|
|
504
|
+
|
|
505
|
+
process.stdout.write('[resolveColumns()]\n');
|
|
506
|
+
out(' value', String(resolveColumns()));
|
|
507
|
+
process.stdout.write('\n');
|
|
508
|
+
|
|
509
|
+
// ANSI 検証: 画面クリア系シーケンスが視覚的にどう動くかを判定する
|
|
510
|
+
// 小テスト。ユーザーには生出力を見て「積み上がっているか」報告してもらう。
|
|
511
|
+
process.stdout.write('[ANSI probe — 視認用]\n');
|
|
512
|
+
process.stdout.write(' 直後に 3 回フレームを書きます。各フレームは clearScreen で上書きされるはず。\n');
|
|
513
|
+
process.stdout.write(' スクリーンショットを取って、フレーム A/B/C が「積み上がり」か「上書き」か教えてください。\n\n');
|
|
514
|
+
|
|
515
|
+
let frame = 0;
|
|
516
|
+
const labels = ['A', 'B', 'C'];
|
|
517
|
+
const probe = () => {
|
|
518
|
+
process.stdout.write(ANSI.clearScreen);
|
|
519
|
+
process.stdout.write(`=== frame ${labels[frame]} / 3 ===\n`);
|
|
520
|
+
process.stdout.write('この行より上に「=== frame X ===」が 2 つ以上見える場合、\n');
|
|
521
|
+
process.stdout.write(`\\x1b[2J\\x1b[3J\\x1b[H (現行の clearScreen) がこの端末で効いていません。\n`);
|
|
522
|
+
process.stdout.write(`現行 clearScreen = 0x1b[2J 0x1b[3J 0x1b[H\n`);
|
|
523
|
+
frame++;
|
|
524
|
+
if (frame >= labels.length) {
|
|
525
|
+
process.stdout.write('\n=== diag 終了 ===\n');
|
|
526
|
+
process.stdout.write('上記の値と、この 3 フレームが積み上がったかどうかを報告してください。\n');
|
|
527
|
+
process.exit(0);
|
|
528
|
+
} else {
|
|
529
|
+
setTimeout(probe, 1500);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
setTimeout(probe, 500);
|
|
533
|
+
}
|
|
534
|
+
|
|
465
535
|
export function main() {
|
|
466
536
|
let args;
|
|
467
537
|
try {
|
|
@@ -472,6 +542,11 @@ export function main() {
|
|
|
472
542
|
process.exit(2);
|
|
473
543
|
}
|
|
474
544
|
|
|
545
|
+
if (args.diag) {
|
|
546
|
+
runDiagnostic();
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
475
550
|
process.stdout.write(ANSI.hideCursor);
|
|
476
551
|
process.stdout.write(color(ANSI.dim, `[Throughline] モニター起動 (state: ${getStateDir()}, Ctrl+C で終了)\n`));
|
|
477
552
|
|
|
@@ -26,19 +26,23 @@ const CWD_BAR = normalizeProjectPath('/tmp/bar');
|
|
|
26
26
|
// ─── parseArgs ─────────────────────────────────────────────────────
|
|
27
27
|
|
|
28
28
|
test('parseArgs: 引数なしは defaults', () => {
|
|
29
|
-
assert.deepEqual(parseArgs([]), { all: false, session: null });
|
|
29
|
+
assert.deepEqual(parseArgs([]), { all: false, session: null, diag: false });
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
test('parseArgs: --all フラグ', () => {
|
|
33
|
-
assert.deepEqual(parseArgs(['--all']), { all: true, session: null });
|
|
33
|
+
assert.deepEqual(parseArgs(['--all']), { all: true, session: null, diag: false });
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
test('parseArgs: --session <id>', () => {
|
|
37
|
-
assert.deepEqual(parseArgs(['--session', 'abc123']), { all: false, session: 'abc123' });
|
|
37
|
+
assert.deepEqual(parseArgs(['--session', 'abc123']), { all: false, session: 'abc123', diag: false });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('parseArgs: --diag フラグ (環境診断モード)', () => {
|
|
41
|
+
assert.deepEqual(parseArgs(['--diag']), { all: false, session: null, diag: true });
|
|
38
42
|
});
|
|
39
43
|
|
|
40
44
|
test('parseArgs: --all と --session の組み合わせ', () => {
|
|
41
|
-
assert.deepEqual(parseArgs(['--all', '--session', 'abc']), { all: true, session: 'abc' });
|
|
45
|
+
assert.deepEqual(parseArgs(['--all', '--session', 'abc']), { all: true, session: 'abc', diag: false });
|
|
42
46
|
});
|
|
43
47
|
|
|
44
48
|
test('parseArgs: --session 値欠落は throw する', () => {
|
|
@@ -51,7 +55,7 @@ test('parseArgs: --session の次が別フラグなら throw する', () => {
|
|
|
51
55
|
|
|
52
56
|
test('parseArgs: 未知の引数は黙殺', () => {
|
|
53
57
|
// 将来 --help などを足す余地を残すため、現状は黙殺で OK
|
|
54
|
-
assert.deepEqual(parseArgs(['--unknown', 'value']), { all: false, session: null });
|
|
58
|
+
assert.deepEqual(parseArgs(['--unknown', 'value']), { all: false, session: null, diag: false });
|
|
55
59
|
});
|
|
56
60
|
|
|
57
61
|
// ─── filterStates ─────────────────────────────────────────────────
|