throughline 0.3.24 → 0.4.0
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/.claude/commands/tl.md +6 -21
- package/.codex-sidecar.yml +62 -0
- package/CHANGELOG.md +632 -0
- package/README.ja.md +71 -46
- package/README.md +420 -76
- package/bin/throughline.mjs +169 -7
- package/codex/skills/throughline/SKILL.md +157 -0
- package/codex/skills/throughline/agents/openai.yaml +7 -0
- package/docs/INHERITANCE_ON_CLEAR_ONLY.md +159 -0
- package/docs/L1_L2_L3_REDESIGN.md +415 -0
- package/docs/PUBLIC_RELEASE_PLAN.md +185 -0
- package/docs/THROUGHLINE_CLEAR_AUTO_HANDOFF_PLAN.md +286 -0
- package/docs/THROUGHLINE_CODEX_DUAL_SUPPORT.md +249 -0
- package/docs/THROUGHLINE_CODEX_FIRST_ROADMAP.md +555 -0
- package/docs/THROUGHLINE_CODEX_MONITOR_IMPLEMENTATION_PLAN.md +220 -0
- package/docs/THROUGHLINE_CODEX_TRIM_IMPLEMENTATION_PLAN.md +528 -0
- package/docs/THROUGHLINE_CODEX_TRIM_ROLLBACK_FIX_PLAN.md +672 -0
- package/docs/archive/CONCEPT.md +476 -0
- package/docs/archive/EXPERIMENT.md +371 -0
- package/docs/archive/README.md +22 -0
- package/docs/archive/SESSION_LINKING_DESIGN.md +231 -0
- package/docs/archive/THROUGHLINE_NEXT_STEPS.md +134 -0
- package/docs/throughline-codex-trim-rollback-incident-report.md +306 -0
- package/docs/throughline-handoff-context.example.json +57 -0
- package/docs/throughline-rollback-context-trim-insight.md +455 -0
- package/package.json +6 -2
- package/src/baton.mjs +17 -45
- package/src/baton.test.mjs +4 -41
- package/src/cli/codex-capture.mjs +95 -0
- package/src/cli/codex-handoff-model-smoke.mjs +292 -0
- package/src/cli/codex-handoff-model-smoke.test.mjs +262 -0
- package/src/cli/codex-handoff-smoke.mjs +163 -0
- package/src/cli/codex-handoff-smoke.test.mjs +149 -0
- package/src/cli/codex-handoff-start.mjs +291 -0
- package/src/cli/codex-handoff-start.test.mjs +194 -0
- package/src/cli/codex-hook.mjs +276 -0
- package/src/cli/codex-hook.test.mjs +293 -0
- package/src/cli/codex-host-primitive-audit.mjs +110 -0
- package/src/cli/codex-host-primitive-audit.test.mjs +75 -0
- package/src/cli/codex-restore-smoke.mjs +357 -0
- package/src/cli/codex-restore-source-audit.mjs +304 -0
- package/src/cli/codex-resume.mjs +138 -0
- package/src/cli/codex-rollback-model-visible-smoke.mjs +373 -0
- package/src/cli/codex-rollback-model-visible-smoke.test.mjs +255 -0
- package/src/cli/codex-sidecar-diagnostics.mjs +48 -0
- package/src/cli/codex-sidecar-dry-run.mjs +85 -0
- package/src/cli/codex-summarize.mjs +224 -0
- package/src/cli/codex-threads.mjs +89 -0
- package/src/cli/codex-visibility-smoke.mjs +196 -0
- package/src/cli/codex-vscode-restore-smoke.mjs +226 -0
- package/src/cli/codex-vscode-rollback-smoke.mjs +114 -0
- package/src/cli/doctor.mjs +503 -1
- package/src/cli/doctor.test.mjs +542 -3
- package/src/cli/handoff-preview.mjs +78 -0
- package/src/cli/help.test.mjs +64 -0
- package/src/cli/install.mjs +226 -3
- package/src/cli/install.test.mjs +205 -4
- package/src/cli/trim.mjs +564 -0
- package/src/codex-app-server.mjs +1816 -0
- package/src/codex-app-server.test.mjs +512 -0
- package/src/codex-auto-refresh.mjs +194 -0
- package/src/codex-auto-refresh.test.mjs +182 -0
- package/src/codex-capture.mjs +235 -0
- package/src/codex-capture.test.mjs +393 -0
- package/src/codex-handoff-model-smoke.mjs +114 -0
- package/src/codex-handoff-model-smoke.test.mjs +89 -0
- package/src/codex-handoff-smoke.mjs +124 -0
- package/src/codex-handoff-smoke.test.mjs +103 -0
- package/src/codex-handoff.mjs +331 -0
- package/src/codex-handoff.test.mjs +220 -0
- package/src/codex-host-primitive-audit.mjs +374 -0
- package/src/codex-host-primitive-audit.test.mjs +208 -0
- package/src/codex-restore-smoke.test.mjs +639 -0
- package/src/codex-restore-source-audit.mjs +1348 -0
- package/src/codex-restore-source-audit.test.mjs +623 -0
- package/src/codex-resume.test.mjs +242 -0
- package/src/codex-rollout-memory.mjs +711 -0
- package/src/codex-rollout-memory.test.mjs +610 -0
- package/src/codex-sidecar-cli.test.mjs +75 -0
- package/src/codex-sidecar.mjs +246 -0
- package/src/codex-sidecar.test.mjs +172 -0
- package/src/codex-summarize.test.mjs +143 -0
- package/src/codex-thread-identity.mjs +23 -0
- package/src/codex-thread-index.mjs +173 -0
- package/src/codex-thread-index.test.mjs +164 -0
- package/src/codex-usage.mjs +110 -0
- package/src/codex-usage.test.mjs +140 -0
- package/src/codex-visibility-smoke.test.mjs +222 -0
- package/src/codex-vscode-restore-smoke.mjs +206 -0
- package/src/codex-vscode-restore-smoke.test.mjs +325 -0
- package/src/codex-vscode-rollback-smoke.mjs +90 -0
- package/src/codex-vscode-rollback-smoke.test.mjs +290 -0
- package/src/db-schema.test.mjs +96 -0
- package/src/db.mjs +14 -1
- package/src/haiku-summarizer.mjs +267 -26
- package/src/haiku-summarizer.test.mjs +282 -0
- package/src/handoff-preview.test.mjs +108 -0
- package/src/handoff-record.mjs +294 -0
- package/src/handoff-record.test.mjs +226 -0
- package/src/hook-entrypoints.test.mjs +286 -0
- package/src/package-files.test.mjs +19 -0
- package/src/prompt-submit.mjs +9 -6
- package/src/resume-context.mjs +58 -171
- package/src/resume-context.test.mjs +177 -0
- package/src/session-start.mjs +85 -26
- package/src/state-file.mjs +50 -6
- package/src/state-file.test.mjs +50 -0
- package/src/token-monitor.mjs +14 -10
- package/src/token-monitor.test.mjs +27 -0
- package/src/trim-cli.test.mjs +1584 -0
- package/src/trim-model.mjs +584 -0
- package/src/trim-model.test.mjs +568 -0
- package/src/turn-processor.mjs +17 -10
- package/src/vscode-task.mjs +33 -10
- package/src/vscode-task.test.mjs +19 -9
- package/src/cli/save-inflight.mjs +0 -81
package/src/state-file.mjs
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* state-file.mjs — セッション単位の状態ファイル管理(共有モジュール)
|
|
3
3
|
*
|
|
4
4
|
* パス: ~/.throughline/state/<session_id>.json
|
|
5
|
-
* 書き手: turn-processor (Stop)
|
|
5
|
+
* 書き手: turn-processor (Claude Stop), codex-hook (Codex Stop)
|
|
6
6
|
* 読み手: token-monitor
|
|
7
7
|
*
|
|
8
8
|
* 設計判断 (docs/PUBLIC_RELEASE_PLAN.md §4.5/4.6):
|
|
9
9
|
* - ファイル単位分割で last-writer-wins 問題を解消
|
|
10
|
-
* -
|
|
10
|
+
* - updatedAt ベースで stale 判定(短命 hook process の PID には依存しない)
|
|
11
11
|
* - projectPath は path.resolve → / → 末尾 / 除去 → Windows lowercase で正規化
|
|
12
12
|
*/
|
|
13
13
|
|
|
@@ -37,21 +37,43 @@ export function normalizeProjectPath(p) {
|
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* セッション状態ファイルを書く
|
|
40
|
-
* @param {{
|
|
40
|
+
* @param {{
|
|
41
|
+
* sessionId: string,
|
|
42
|
+
* projectPath: string,
|
|
43
|
+
* transcriptPath?: string|null,
|
|
44
|
+
* rolloutPath?: string|null,
|
|
45
|
+
* pid?: number,
|
|
46
|
+
* usage?: object|null,
|
|
47
|
+
* host?: 'claude'|'codex',
|
|
48
|
+
* }} data
|
|
41
49
|
*
|
|
42
50
|
* usage: monitor が表示する tokens/model/contextWindowSize をここに固定保存する。
|
|
43
51
|
* Stop hook が readLatestUsage の結果を載せることで、monitor 側が毎フレーム JSONL を
|
|
44
52
|
* 再スキャンする必要がなくなる。旧バージョン互換のため optional (無ければ monitor が
|
|
45
53
|
* transcriptPath を読んでフォールバック)。
|
|
46
54
|
*/
|
|
47
|
-
export function writeSessionState({
|
|
55
|
+
export function writeSessionState({
|
|
56
|
+
sessionId,
|
|
57
|
+
projectPath,
|
|
58
|
+
transcriptPath,
|
|
59
|
+
rolloutPath,
|
|
60
|
+
pid,
|
|
61
|
+
usage,
|
|
62
|
+
host,
|
|
63
|
+
}) {
|
|
48
64
|
if (!sessionId) throw new Error('writeSessionState: sessionId is required');
|
|
65
|
+
const normalizedHost = normalizeHost(host);
|
|
66
|
+
if (host && normalizedHost === 'unknown') {
|
|
67
|
+
throw new Error(`writeSessionState: unsupported host ${host}`);
|
|
68
|
+
}
|
|
49
69
|
if (!existsSync(STATE_DIR)) mkdirSync(STATE_DIR, { recursive: true });
|
|
50
|
-
const file = join(STATE_DIR,
|
|
70
|
+
const file = join(STATE_DIR, stateFilename(sessionId));
|
|
51
71
|
const payload = {
|
|
52
72
|
sessionId,
|
|
73
|
+
host: normalizedHost === 'unknown' ? 'claude' : normalizedHost,
|
|
53
74
|
projectPath: normalizeProjectPath(projectPath),
|
|
54
75
|
transcriptPath: transcriptPath ?? null,
|
|
76
|
+
rolloutPath: rolloutPath ?? null,
|
|
55
77
|
pid: pid ?? process.pid,
|
|
56
78
|
updatedAt: Date.now(),
|
|
57
79
|
};
|
|
@@ -69,7 +91,7 @@ export const STALE_DELETE_MS = 24 * 60 * 60 * 1000; // 24 時間: ファイル
|
|
|
69
91
|
/**
|
|
70
92
|
* 全セッション状態を読む。24 時間超のファイルは削除、壊れたファイルも削除する。
|
|
71
93
|
* 15 分超のファイルは「stale」フラグを付けて返す(monitor 側で隠す判断をする)。
|
|
72
|
-
* @returns {Array<{sessionId: string, projectPath: string, transcriptPath: string|null, updatedAt: number, stale: boolean}>}
|
|
94
|
+
* @returns {Array<{sessionId: string, host: string, projectPath: string, transcriptPath: string|null, rolloutPath: string|null, updatedAt: number, stale: boolean}>}
|
|
73
95
|
*/
|
|
74
96
|
export function readAllSessionStates() {
|
|
75
97
|
if (!existsSync(STATE_DIR)) return [];
|
|
@@ -112,6 +134,7 @@ export function readAllSessionStates() {
|
|
|
112
134
|
}
|
|
113
135
|
continue;
|
|
114
136
|
}
|
|
137
|
+
parsed = normalizeState(parsed);
|
|
115
138
|
const age = now - (parsed.updatedAt ?? 0);
|
|
116
139
|
if (age > STALE_DELETE_MS) {
|
|
117
140
|
// 24h 超: ハード削除(無制限蓄積防止)
|
|
@@ -128,6 +151,27 @@ export function readAllSessionStates() {
|
|
|
128
151
|
return results;
|
|
129
152
|
}
|
|
130
153
|
|
|
154
|
+
function stateFilename(sessionId) {
|
|
155
|
+
return `${encodeURIComponent(sessionId)}.json`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function normalizeHost(host) {
|
|
159
|
+
if (host === undefined || host === null || host === '') return 'claude';
|
|
160
|
+
if (host === 'claude' || host === 'codex') return host;
|
|
161
|
+
return 'unknown';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function normalizeState(parsed) {
|
|
165
|
+
const host = normalizeHost(parsed?.host);
|
|
166
|
+
return {
|
|
167
|
+
...parsed,
|
|
168
|
+
host,
|
|
169
|
+
projectPath: normalizeProjectPath(parsed?.projectPath ?? ''),
|
|
170
|
+
transcriptPath: parsed?.transcriptPath ?? null,
|
|
171
|
+
rolloutPath: parsed?.rolloutPath ?? null,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
131
175
|
/**
|
|
132
176
|
* ファイル単位の mtime スナップショットを取る(差分検知用)
|
|
133
177
|
* @returns {Map<string, number>}
|
package/src/state-file.test.mjs
CHANGED
|
@@ -146,11 +146,58 @@ test('writeSessionState: usage 付きで書くと JSON に含まれる', async (
|
|
|
146
146
|
const results = mod.readAllSessionStates();
|
|
147
147
|
assert.equal(results.length, 1);
|
|
148
148
|
assert.ok(results[0].usage);
|
|
149
|
+
assert.equal(results[0].host, 'claude');
|
|
150
|
+
assert.equal(results[0].rolloutPath, null);
|
|
149
151
|
assert.equal(results[0].usage.tokens, 123);
|
|
150
152
|
assert.equal(results[0].usage.model, 'claude-opus-4-6');
|
|
151
153
|
});
|
|
152
154
|
});
|
|
153
155
|
|
|
156
|
+
test('writeSessionState: Codex state は host と rolloutPath を保持しファイル名を encode する', async () => {
|
|
157
|
+
await withIsolatedStateDir(async ({ stateDir, mod }) => {
|
|
158
|
+
mod.writeSessionState({
|
|
159
|
+
sessionId: 'codex:019dfaba-thread',
|
|
160
|
+
host: 'codex',
|
|
161
|
+
projectPath: '/tmp/x',
|
|
162
|
+
transcriptPath: null,
|
|
163
|
+
rolloutPath: '/tmp/codex/rollout.jsonl',
|
|
164
|
+
pid: 1,
|
|
165
|
+
usage: {
|
|
166
|
+
tokens: 123,
|
|
167
|
+
model: 'codex',
|
|
168
|
+
contextWindowSize: 258400,
|
|
169
|
+
contextWindowEstimated: false,
|
|
170
|
+
outputTokens: 10,
|
|
171
|
+
estimated: false,
|
|
172
|
+
source: 'codex-rollout-token-count',
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
assert.deepEqual(readdirSync(stateDir), ['codex%3A019dfaba-thread.json']);
|
|
177
|
+
const results = mod.readAllSessionStates();
|
|
178
|
+
assert.equal(results.length, 1);
|
|
179
|
+
assert.equal(results[0].sessionId, 'codex:019dfaba-thread');
|
|
180
|
+
assert.equal(results[0].host, 'codex');
|
|
181
|
+
assert.equal(results[0].transcriptPath, null);
|
|
182
|
+
assert.equal(results[0].rolloutPath, '/tmp/codex/rollout.jsonl');
|
|
183
|
+
assert.equal(results[0].usage.source, 'codex-rollout-token-count');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('writeSessionState: unsupported host は throw する', async () => {
|
|
188
|
+
await withIsolatedStateDir(async ({ mod }) => {
|
|
189
|
+
assert.throws(
|
|
190
|
+
() =>
|
|
191
|
+
mod.writeSessionState({
|
|
192
|
+
sessionId: 'sess-bad-host',
|
|
193
|
+
host: 'unknown-host',
|
|
194
|
+
projectPath: '/tmp/x',
|
|
195
|
+
}),
|
|
196
|
+
/unsupported host/,
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
154
201
|
test('writeSessionState: usage 無しで書いたらフィールド自体が無い (旧フォーマット互換)', async () => {
|
|
155
202
|
await withIsolatedStateDir(async ({ stateDir, mod }) => {
|
|
156
203
|
mod.writeSessionState({
|
|
@@ -162,6 +209,7 @@ test('writeSessionState: usage 無しで書いたらフィールド自体が無
|
|
|
162
209
|
const results = mod.readAllSessionStates();
|
|
163
210
|
assert.equal(results.length, 1);
|
|
164
211
|
assert.equal(results[0].usage, undefined);
|
|
212
|
+
assert.equal(results[0].host, 'claude');
|
|
165
213
|
});
|
|
166
214
|
});
|
|
167
215
|
|
|
@@ -178,6 +226,8 @@ test('readAllSessionStates: 旧バージョンが書いた usage 無しの state
|
|
|
178
226
|
}));
|
|
179
227
|
const results = mod.readAllSessionStates();
|
|
180
228
|
assert.equal(results.length, 1);
|
|
229
|
+
assert.equal(results[0].host, 'claude');
|
|
230
|
+
assert.equal(results[0].rolloutPath, null);
|
|
181
231
|
assert.equal(results[0].usage, undefined);
|
|
182
232
|
// usage 無しで読めること自体が互換性の証明
|
|
183
233
|
});
|
package/src/token-monitor.mjs
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* - setInterval (1s) + mtime 差分検知で更新を捕捉
|
|
15
15
|
* - updatedAt 降順ソート、先頭行を ▶ でハイライト
|
|
16
16
|
* - stale は PID 生存チェックで判定
|
|
17
|
-
* -
|
|
17
|
+
* - Claude は transcript JSONL の最新 assistant usage を直読
|
|
18
|
+
* - Codex は Stop hook が state.usage に固定した rollout usage / estimate を表示
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
import { basename, dirname, join } from 'node:path';
|
|
@@ -191,7 +192,7 @@ function parseArgs(argv) {
|
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
// --- 表示 ---
|
|
194
|
-
function renderBar(ratio, width =
|
|
195
|
+
function renderBar(ratio, width = 10) {
|
|
195
196
|
// NaN は 0、+Infinity は 1(オーバーフロー = 満タン表示)、負値 / -Infinity は 0 にクランプ
|
|
196
197
|
let safe;
|
|
197
198
|
if (Number.isNaN(ratio)) safe = 0;
|
|
@@ -288,11 +289,10 @@ export function resolveColumns() {
|
|
|
288
289
|
function formatLine({ state, usage, isActive, now = Date.now() }) {
|
|
289
290
|
const project = basename(state.projectPath || '?');
|
|
290
291
|
const shortId = state.sessionId.slice(0, 8);
|
|
292
|
+
const host = state.host === 'codex' ? 'Codex' : state.host === 'unknown' ? 'Unknown' : 'Claude';
|
|
291
293
|
const tokens = usage?.tokens ?? 0;
|
|
292
294
|
const max = usage?.contextWindowSize ?? 200_000;
|
|
293
295
|
const ratio = max > 0 ? tokens / max : 0;
|
|
294
|
-
const pct = Math.round(ratio * 100);
|
|
295
|
-
const remaining = Math.max(0, max - tokens);
|
|
296
296
|
|
|
297
297
|
const bar = renderBar(ratio);
|
|
298
298
|
const barColor =
|
|
@@ -310,11 +310,15 @@ function formatLine({ state, usage, isActive, now = Date.now() }) {
|
|
|
310
310
|
|
|
311
311
|
const marker = isActive ? color(ANSI.bold + ANSI.cyan, '▶') : ' ';
|
|
312
312
|
const projectCol = padCellsEnd(project, 18);
|
|
313
|
+
const hostCol = color(ANSI.dim, padCellsEnd(host, 6));
|
|
313
314
|
const idCol = color(ANSI.dim, shortId);
|
|
314
315
|
const barCol = color(barColor, bar);
|
|
315
|
-
const tokCol = `${formatNumber(tokens).padStart(6)} / ${
|
|
316
|
-
const
|
|
317
|
-
const
|
|
316
|
+
const tokCol = `${formatNumber(tokens).padStart(6)} / ${formatNumber(max).padStart(6)}`;
|
|
317
|
+
const estimateMark = usage?.estimated ? ' est' : '';
|
|
318
|
+
const windowMark = usage?.contextWindowEstimated ? ' win?' : '';
|
|
319
|
+
const modelCol = usage?.model
|
|
320
|
+
? color(ANSI.dim, `${usage.model}${estimateMark}${windowMark}`)
|
|
321
|
+
: color(ANSI.dim, '(未取得)');
|
|
318
322
|
// 最終更新からの経過: 表示が「止まって見える」とき、それが idle なのか障害なのかを
|
|
319
323
|
// 即座に判別できるようにする。updatedAt は state.writeSessionState 時の Date.now()。
|
|
320
324
|
// 位置は session id の直後(左寄せ固定幅)。狭いターミナルでもモデル名より先に
|
|
@@ -325,7 +329,7 @@ function formatLine({ state, usage, isActive, now = Date.now() }) {
|
|
|
325
329
|
// 8 セル固定: "just now" が最長 (8 セル)、"99d ago" は 7 セル。括弧なしで OK
|
|
326
330
|
const agoCol = color(ANSI.dim, padCellsEnd(agoText, 8));
|
|
327
331
|
|
|
328
|
-
return `${marker} ${projectCol} ${idCol} ${agoCol} ${barCol} ${tokCol} ${
|
|
332
|
+
return `${marker} ${projectCol} ${hostCol} ${idCol} ${agoCol} ${barCol} ${tokCol} ${modelCol}${warn}`;
|
|
329
333
|
}
|
|
330
334
|
|
|
331
335
|
// --- フィルタ ---
|
|
@@ -428,8 +432,8 @@ function renderFrame(args) {
|
|
|
428
432
|
for (let i = 0; i < filtered.length; i++) {
|
|
429
433
|
const state = filtered[i];
|
|
430
434
|
// Stop hook が state.usage に固定値を入れていればそれを使う(JSONL 再スキャン不要)。
|
|
431
|
-
// 旧バージョンが書いた state や usage スナップショットが取れなかったターンでは
|
|
432
|
-
// transcriptPath
|
|
435
|
+
// 旧バージョンが書いた Claude state や usage スナップショットが取れなかったターンでは
|
|
436
|
+
// transcriptPath を直読。state 側の情報が 1 本化されると
|
|
433
437
|
// 「state が古い JSONL を指している」時の表示ブレが減る。
|
|
434
438
|
const usage = state.usage
|
|
435
439
|
?? (state.transcriptPath ? readLatestUsage(state.transcriptPath) : null);
|
|
@@ -254,6 +254,10 @@ test('renderBar: ratio=0 は全部 ░', () => {
|
|
|
254
254
|
assert.equal(renderBar(0, 5), '░░░░░');
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
+
test('renderBar: default width は 10 セル', () => {
|
|
258
|
+
assert.equal(renderBar(0.5), '█████░░░░░');
|
|
259
|
+
});
|
|
260
|
+
|
|
257
261
|
test('renderBar: ratio=1 は全部 █', () => {
|
|
258
262
|
assert.equal(renderBar(1, 5), '█████');
|
|
259
263
|
});
|
|
@@ -287,6 +291,7 @@ function makeLineArgs(ratio) {
|
|
|
287
291
|
return {
|
|
288
292
|
state: {
|
|
289
293
|
sessionId: 'abc12345-xxxx',
|
|
294
|
+
host: 'claude',
|
|
290
295
|
projectPath: '/tmp/foo',
|
|
291
296
|
transcriptPath: null,
|
|
292
297
|
updatedAt: Date.now(),
|
|
@@ -303,11 +308,33 @@ function makeLineArgs(ratio) {
|
|
|
303
308
|
|
|
304
309
|
test('formatLine: 70% 未満は警告テキストなし', () => {
|
|
305
310
|
const out = stripColors(formatLine(makeLineArgs(0.5)));
|
|
311
|
+
assert.ok(out.includes('Claude'));
|
|
312
|
+
assert.ok(out.includes('100.0k / 200.0k'));
|
|
313
|
+
assert.ok(!out.includes('残'));
|
|
314
|
+
assert.ok(!out.includes('/ 50%'));
|
|
306
315
|
assert.ok(!out.includes('!!'));
|
|
307
316
|
assert.ok(!out.includes('! '));
|
|
308
317
|
assert.ok(!out.includes('/tl'));
|
|
309
318
|
});
|
|
310
319
|
|
|
320
|
+
test('formatLine: Codex estimated usage は host と est marker を表示する', () => {
|
|
321
|
+
const args = makeLineArgs(0.5);
|
|
322
|
+
args.state.host = 'codex';
|
|
323
|
+
args.usage = {
|
|
324
|
+
tokens: 100_000,
|
|
325
|
+
model: 'codex',
|
|
326
|
+
contextWindowSize: 200_000,
|
|
327
|
+
contextWindowEstimated: true,
|
|
328
|
+
outputTokens: 0,
|
|
329
|
+
estimated: true,
|
|
330
|
+
source: 'codex-rollout-chars-div-4',
|
|
331
|
+
};
|
|
332
|
+
const out = stripColors(formatLine(args));
|
|
333
|
+
assert.ok(out.includes('Codex'));
|
|
334
|
+
assert.ok(out.includes('100.0k / 200.0k'));
|
|
335
|
+
assert.ok(out.includes('codex est win?'));
|
|
336
|
+
});
|
|
337
|
+
|
|
311
338
|
test('formatLine: 70% 以上で "!" マーカーと弱めの文言', () => {
|
|
312
339
|
const out = stripColors(formatLine(makeLineArgs(0.75)));
|
|
313
340
|
assert.ok(out.includes('!'), 'should include ! marker');
|