throughline 0.3.10 → 0.3.12
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 +90 -5
- package/src/token-monitor.test.mjs +9 -5
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
|
|
|
@@ -34,7 +35,11 @@ const ANSI = {
|
|
|
34
35
|
hideCursor: '\x1b[?25l',
|
|
35
36
|
showCursor: '\x1b[?25h',
|
|
36
37
|
clearLine: '\x1b[2K',
|
|
37
|
-
|
|
38
|
+
// xterm.js は `\x1b[2J` (ED2) をビューポート消去としてしか実装しておらず、
|
|
39
|
+
// 消えた内容はスクロールバックに残る(xterm.js issue #5019 の jerch コメント参照)。
|
|
40
|
+
// VSCode task panel でフレーム更新が「積み上がる」ように見えるのはこれが原因。
|
|
41
|
+
// `\x1b[3J` (ED3) を続けて送るとスクロールバックも消えて真の全クリアになる。
|
|
42
|
+
clearScreen: '\x1b[2J\x1b[3J\x1b[H',
|
|
38
43
|
clearBelow: '\x1b[0J', // 現在位置から画面末尾までをクリア
|
|
39
44
|
up: (n) => `\x1b[${n}A`, // CUU: カーソルを N 行上へ (列は変えない)
|
|
40
45
|
reset: '\x1b[0m',
|
|
@@ -162,14 +167,16 @@ function padCellsEnd(s, targetCells) {
|
|
|
162
167
|
// --- CLI 引数 ---
|
|
163
168
|
/**
|
|
164
169
|
* @param {string[]} argv
|
|
165
|
-
* @returns {{all: boolean, session: string|null}}
|
|
170
|
+
* @returns {{all: boolean, session: string|null, diag: boolean}}
|
|
166
171
|
* @throws {Error} --session に値が欠落している場合
|
|
167
172
|
*/
|
|
168
173
|
function parseArgs(argv) {
|
|
169
|
-
const args = { all: false, session: null };
|
|
174
|
+
const args = { all: false, session: null, diag: false };
|
|
170
175
|
for (let i = 0; i < argv.length; i++) {
|
|
171
176
|
if (argv[i] === '--all') {
|
|
172
177
|
args.all = true;
|
|
178
|
+
} else if (argv[i] === '--diag') {
|
|
179
|
+
args.diag = true;
|
|
173
180
|
} else if (argv[i] === '--session') {
|
|
174
181
|
const value = argv[i + 1];
|
|
175
182
|
if (value === undefined || value.startsWith('--')) {
|
|
@@ -462,6 +469,79 @@ function safeRenderFrame(args) {
|
|
|
462
469
|
}
|
|
463
470
|
}
|
|
464
471
|
|
|
472
|
+
/**
|
|
473
|
+
* 環境診断モード。ユーザー環境で「モニターの描画が壊れる」と報告されたときに、
|
|
474
|
+
* 実際の process.stdout / env / TERM の値をその場で可視化するための一発起動モード。
|
|
475
|
+
*
|
|
476
|
+
* 過去に「`type: process` だと非 TTY」「`type: shell` なら PTY」など推測で描画戦略を
|
|
477
|
+
* 変えてきたが、PTY が張られるかどうかは VSCode のバージョンや Windows の ConPTY
|
|
478
|
+
* 実装に依存し、推測は外れ続けた。このコマンドで実測値を 1 ページに出すことで、
|
|
479
|
+
* 「この環境では何が起きているか」を断定できるようにする。
|
|
480
|
+
*
|
|
481
|
+
* 出力は ~/.throughline/last-diag.txt にも保存する。task terminal のようにスクロール
|
|
482
|
+
* バックが分かりづらい環境でも後からファイルを読めば全情報を回収できる。
|
|
483
|
+
*/
|
|
484
|
+
function runDiagnostic() {
|
|
485
|
+
const lines = [];
|
|
486
|
+
const emit = (s) => {
|
|
487
|
+
process.stdout.write(s);
|
|
488
|
+
lines.push(s);
|
|
489
|
+
};
|
|
490
|
+
const out = (k, v) => emit(`${k.padEnd(28)}${v}\n`);
|
|
491
|
+
|
|
492
|
+
emit('=== Throughline monitor diagnostic ===\n');
|
|
493
|
+
emit(`timestamp: ${new Date().toISOString()}\n\n`);
|
|
494
|
+
|
|
495
|
+
emit('[process.stdout]\n');
|
|
496
|
+
out(' isTTY', String(Boolean(process.stdout.isTTY)));
|
|
497
|
+
out(' columns', String(process.stdout.columns ?? '(undefined)'));
|
|
498
|
+
out(' rows', String(process.stdout.rows ?? '(undefined)'));
|
|
499
|
+
out(' hasColors()',
|
|
500
|
+
typeof process.stdout.hasColors === 'function'
|
|
501
|
+
? String(process.stdout.hasColors())
|
|
502
|
+
: '(n/a)',
|
|
503
|
+
);
|
|
504
|
+
emit('\n');
|
|
505
|
+
|
|
506
|
+
emit('[process.stderr]\n');
|
|
507
|
+
out(' isTTY', String(Boolean(process.stderr.isTTY)));
|
|
508
|
+
emit('\n');
|
|
509
|
+
|
|
510
|
+
emit('[env]\n');
|
|
511
|
+
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']) {
|
|
512
|
+
out(` ${key}`, process.env[key] ?? '(unset)');
|
|
513
|
+
}
|
|
514
|
+
emit('\n');
|
|
515
|
+
|
|
516
|
+
emit('[resolveColumns()]\n');
|
|
517
|
+
out(' value', String(resolveColumns()));
|
|
518
|
+
emit('\n');
|
|
519
|
+
|
|
520
|
+
emit('[platform]\n');
|
|
521
|
+
out(' process.platform', process.platform);
|
|
522
|
+
out(' process.versions.node', process.versions.node);
|
|
523
|
+
emit('\n');
|
|
524
|
+
|
|
525
|
+
emit('=== end of diag ===\n');
|
|
526
|
+
emit('\nこの出力を全文コピーするか、~/.throughline/last-diag.txt を開いて内容を共有してください。\n');
|
|
527
|
+
emit('task terminal でスクロールして読めない場合は PowerShell で\n');
|
|
528
|
+
emit(' cat ~/.throughline/last-diag.txt\n');
|
|
529
|
+
emit('を実行すれば同じ内容が読めます。\n');
|
|
530
|
+
|
|
531
|
+
// ログファイルに保存 (task terminal のようにスクロール不能な環境向け)
|
|
532
|
+
try {
|
|
533
|
+
const logPath = join(homedir(), '.throughline', 'last-diag.txt');
|
|
534
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
535
|
+
writeFileSync(logPath, lines.join(''));
|
|
536
|
+
emit(`\n[ログ保存] ${logPath}\n`);
|
|
537
|
+
} catch (err) {
|
|
538
|
+
const msg = err instanceof Error ? err.message : 'unknown';
|
|
539
|
+
emit(`\n[ログ保存失敗] ${msg}\n`);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
process.exit(0);
|
|
543
|
+
}
|
|
544
|
+
|
|
465
545
|
export function main() {
|
|
466
546
|
let args;
|
|
467
547
|
try {
|
|
@@ -472,6 +552,11 @@ export function main() {
|
|
|
472
552
|
process.exit(2);
|
|
473
553
|
}
|
|
474
554
|
|
|
555
|
+
if (args.diag) {
|
|
556
|
+
runDiagnostic();
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
475
560
|
process.stdout.write(ANSI.hideCursor);
|
|
476
561
|
process.stdout.write(color(ANSI.dim, `[Throughline] モニター起動 (state: ${getStateDir()}, Ctrl+C で終了)\n`));
|
|
477
562
|
|
|
@@ -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 ─────────────────────────────────────────────────
|