throughline 0.3.9 → 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 +83 -22
- package/src/token-monitor.test.mjs +9 -5
- package/src/vscode-task.mjs +11 -13
- package/src/vscode-task.test.mjs +3 -10
package/package.json
CHANGED
package/src/token-monitor.mjs
CHANGED
|
@@ -33,14 +33,12 @@ let lastTimeAgoRefresh = Date.now();
|
|
|
33
33
|
const ANSI = {
|
|
34
34
|
hideCursor: '\x1b[?25l',
|
|
35
35
|
showCursor: '\x1b[?25h',
|
|
36
|
-
// オルタネートスクリーンバッファ (htop / vim / less が使うやつ)。
|
|
37
|
-
// プライマリバッファでの `\x1b[2J` は xterm.js だと「描画済み行をスクロール履歴に
|
|
38
|
-
// 押し上げる」挙動になり、狭い VSCode task terminal で描画が永遠に積み上がる
|
|
39
|
-
// 症状を引き起こす。alt バッファならスクロール履歴に残らず真にクリアできる。
|
|
40
|
-
enterAltScreen: '\x1b[?1049h',
|
|
41
|
-
leaveAltScreen: '\x1b[?1049l',
|
|
42
36
|
clearLine: '\x1b[2K',
|
|
43
|
-
|
|
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',
|
|
44
42
|
clearBelow: '\x1b[0J', // 現在位置から画面末尾までをクリア
|
|
45
43
|
up: (n) => `\x1b[${n}A`, // CUU: カーソルを N 行上へ (列は変えない)
|
|
46
44
|
reset: '\x1b[0m',
|
|
@@ -168,14 +166,16 @@ function padCellsEnd(s, targetCells) {
|
|
|
168
166
|
// --- CLI 引数 ---
|
|
169
167
|
/**
|
|
170
168
|
* @param {string[]} argv
|
|
171
|
-
* @returns {{all: boolean, session: string|null}}
|
|
169
|
+
* @returns {{all: boolean, session: string|null, diag: boolean}}
|
|
172
170
|
* @throws {Error} --session に値が欠落している場合
|
|
173
171
|
*/
|
|
174
172
|
function parseArgs(argv) {
|
|
175
|
-
const args = { all: false, session: null };
|
|
173
|
+
const args = { all: false, session: null, diag: false };
|
|
176
174
|
for (let i = 0; i < argv.length; i++) {
|
|
177
175
|
if (argv[i] === '--all') {
|
|
178
176
|
args.all = true;
|
|
177
|
+
} else if (argv[i] === '--diag') {
|
|
178
|
+
args.diag = true;
|
|
179
179
|
} else if (argv[i] === '--session') {
|
|
180
180
|
const value = argv[i + 1];
|
|
181
181
|
if (value === undefined || value.startsWith('--')) {
|
|
@@ -447,18 +447,12 @@ function renderFrame(args) {
|
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
// --- 起動 ---
|
|
450
|
-
let
|
|
451
|
-
/**
|
|
452
|
-
* 終了時に端末状態を元に戻す:
|
|
453
|
-
* - オルタネートスクリーンバッファから抜ける (起動前の画面に戻る)
|
|
454
|
-
* - カーソル表示を復活
|
|
455
|
-
* 2 回以上呼ばれても安全 (冪等)。
|
|
456
|
-
*/
|
|
450
|
+
let cursorRestored = false;
|
|
457
451
|
function restoreCursor() {
|
|
458
|
-
if (
|
|
459
|
-
|
|
452
|
+
if (cursorRestored) return;
|
|
453
|
+
cursorRestored = true;
|
|
460
454
|
try {
|
|
461
|
-
process.stdout.write(ANSI.
|
|
455
|
+
process.stdout.write(ANSI.showCursor);
|
|
462
456
|
} catch {
|
|
463
457
|
// stdout がすでに閉じていても無視
|
|
464
458
|
}
|
|
@@ -474,6 +468,70 @@ function safeRenderFrame(args) {
|
|
|
474
468
|
}
|
|
475
469
|
}
|
|
476
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
|
+
|
|
477
535
|
export function main() {
|
|
478
536
|
let args;
|
|
479
537
|
try {
|
|
@@ -484,9 +542,12 @@ export function main() {
|
|
|
484
542
|
process.exit(2);
|
|
485
543
|
}
|
|
486
544
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
545
|
+
if (args.diag) {
|
|
546
|
+
runDiagnostic();
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
process.stdout.write(ANSI.hideCursor);
|
|
490
551
|
process.stdout.write(color(ANSI.dim, `[Throughline] モニター起動 (state: ${getStateDir()}, Ctrl+C で終了)\n`));
|
|
491
552
|
|
|
492
553
|
safeRenderFrame(args);
|
|
@@ -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 ─────────────────────────────────────────────────
|
package/src/vscode-task.mjs
CHANGED
|
@@ -105,27 +105,25 @@ export function hasMonitorTask(obj) {
|
|
|
105
105
|
/**
|
|
106
106
|
* 生成する VSCode タスク定義を組み立てる。
|
|
107
107
|
*
|
|
108
|
-
* type: '
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
108
|
+
* type: 'shell' を選ぶ理由:
|
|
109
|
+
* VSCode の type: 'process' は子プロセスの stdout を生パイプで渡すため、
|
|
110
|
+
* isTTY=false となり真の columns が取れず、`\x1b[2J\x1b[H` や alt screen も
|
|
111
|
+
* 期待通りに動かず、モニターの行描画が毎ティック積み上がる症状を引き起こす。
|
|
112
|
+
* type: 'shell' なら VSCode が PTY を張って xterm.js ベースの疑似端末を与え、
|
|
113
|
+
* isTTY=true で真の幅が取れ、resize イベントも発火する。
|
|
112
114
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* resolveColumns() が 200 を採用して行が「openclaw」で切れる不具合を避けられる。
|
|
115
|
+
* Windows の .cmd shim リスクは「command / args を shell の実行ファイル
|
|
116
|
+
* (cmd.exe / bash) に絶対パスで渡す」ので消えている。command に node.exe の
|
|
117
|
+
* 絶対パス、args に throughline.mjs の絶対パスと 'monitor' を入れ、shell
|
|
118
|
+
* (cmd.exe や pwsh) がそのまま起動するだけ。
|
|
118
119
|
*/
|
|
119
120
|
export function buildMonitorTask(throughlineBin) {
|
|
120
121
|
return {
|
|
121
122
|
label: MONITOR_LABEL,
|
|
122
123
|
detail: 'Auto-generated by Throughline. Opens token-monitor in a dedicated terminal when the folder is opened.',
|
|
123
|
-
type: '
|
|
124
|
+
type: 'shell',
|
|
124
125
|
command: process.execPath,
|
|
125
126
|
args: [throughlineBin, 'monitor'],
|
|
126
|
-
options: {
|
|
127
|
-
env: { COLUMNS: '200' },
|
|
128
|
-
},
|
|
129
127
|
isBackground: true,
|
|
130
128
|
presentation: {
|
|
131
129
|
reveal: 'always',
|
package/src/vscode-task.test.mjs
CHANGED
|
@@ -138,23 +138,16 @@ test('hasMonitorTask: handles missing tasks array', () => {
|
|
|
138
138
|
|
|
139
139
|
// --- buildMonitorTask ---
|
|
140
140
|
|
|
141
|
-
test('buildMonitorTask: uses type=
|
|
141
|
+
test('buildMonitorTask: uses type=shell with provided bin as args[0] for PTY allocation', () => {
|
|
142
142
|
const task = buildMonitorTask('/abs/bin/throughline.mjs');
|
|
143
143
|
assert.equal(task.label, 'Throughline Monitor');
|
|
144
|
-
assert.equal(task.type, '
|
|
144
|
+
assert.equal(task.type, 'shell');
|
|
145
145
|
assert.equal(task.args[0], '/abs/bin/throughline.mjs');
|
|
146
146
|
assert.deepEqual(task.args.slice(1), ['monitor']);
|
|
147
147
|
assert.equal(task.runOptions.runOn, 'folderOpen');
|
|
148
148
|
assert.equal(task.isBackground, true);
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
-
test('buildMonitorTask: sets COLUMNS=200 env to work around type:process non-TTY stdout', () => {
|
|
152
|
-
const task = buildMonitorTask('/abs/bin/throughline.mjs');
|
|
153
|
-
assert.ok(task.options, 'task should carry options');
|
|
154
|
-
assert.ok(task.options.env, 'options should carry env');
|
|
155
|
-
assert.equal(task.options.env.COLUMNS, '200');
|
|
156
|
-
});
|
|
157
|
-
|
|
158
151
|
// --- ensureMonitorTaskFile: skip conditions ---
|
|
159
152
|
|
|
160
153
|
test('ensureMonitorTaskFile: opt_out via THROUGHLINE_NO_VSCODE=1', () => {
|
|
@@ -239,7 +232,7 @@ test('ensureMonitorTaskFile: created when .vscode/ missing', () => {
|
|
|
239
232
|
assert.equal(obj.version, '2.0.0');
|
|
240
233
|
assert.equal(obj.tasks.length, 1);
|
|
241
234
|
assert.equal(obj.tasks[0].label, 'Throughline Monitor');
|
|
242
|
-
assert.equal(obj.tasks[0].type, '
|
|
235
|
+
assert.equal(obj.tasks[0].type, 'shell');
|
|
243
236
|
assert.equal(obj.tasks[0].args[0], FAKE_BIN);
|
|
244
237
|
} finally {
|
|
245
238
|
cleanup();
|