throughline 0.3.24 → 0.3.25
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-trim.md +42 -0
- package/.codex-sidecar.yml +62 -0
- package/CHANGELOG.md +583 -0
- package/README.ja.md +42 -5
- package/README.md +383 -23
- package/bin/throughline.mjs +168 -4
- package/codex/skills/throughline/SKILL.md +157 -0
- package/codex/skills/throughline/agents/openai.yaml +7 -0
- package/docs/INHERITANCE_ON_CLEAR_ONLY.md +146 -0
- package/docs/L1_L2_L3_REDESIGN.md +415 -0
- package/docs/PUBLIC_RELEASE_PLAN.md +184 -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/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 +227 -4
- package/src/cli/install.test.mjs +207 -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 +97 -0
- 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 +326 -0
- package/src/package-files.test.mjs +19 -0
- package/src/prompt-submit.mjs +9 -6
- package/src/resume-context.mjs +44 -140
- package/src/resume-context.test.mjs +172 -0
- package/src/session-start.mjs +8 -5
- 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/doctor.mjs
CHANGED
|
@@ -19,6 +19,16 @@ import { homedir } from 'node:os';
|
|
|
19
19
|
import { execSync } from 'node:child_process';
|
|
20
20
|
import { getStateDir } from '../state-file.mjs';
|
|
21
21
|
import { readLatestUsage } from '../transcript-usage.mjs';
|
|
22
|
+
import { buildCodexRolloutTrimSource } from '../codex-rollout-memory.mjs';
|
|
23
|
+
import { runCodexHostPrimitiveAudit } from '../codex-host-primitive-audit.mjs';
|
|
24
|
+
import { buildCodexHandoffSmoke } from '../codex-handoff-smoke.mjs';
|
|
25
|
+
import { buildHandoffRecord } from '../handoff-record.mjs';
|
|
26
|
+
import { DEFAULT_TRIM_KEEP_RECENT, buildTrimPlan, describeTrimHost } from '../trim-model.mjs';
|
|
27
|
+
import { resolveCodexThreadIdentity } from '../codex-thread-identity.mjs';
|
|
28
|
+
import { defaultCodexHome, listCodexThreadCandidates } from '../codex-thread-index.mjs';
|
|
29
|
+
import { getDb } from '../db.mjs';
|
|
30
|
+
import { detectJsoncFeatures, findMonitorTaskIndex, isMonitorTaskBroken } from '../vscode-task.mjs';
|
|
31
|
+
import { buildCodexStopHookCommand, isThroughlineCodexStopCommand } from './install.mjs';
|
|
22
32
|
|
|
23
33
|
const GREEN = '\x1b[32m✓\x1b[0m';
|
|
24
34
|
const RED = '\x1b[31m✗\x1b[0m';
|
|
@@ -43,7 +53,7 @@ async function check(label, fn) {
|
|
|
43
53
|
}
|
|
44
54
|
|
|
45
55
|
function parseArgs(argv) {
|
|
46
|
-
const args = { session: null };
|
|
56
|
+
const args = { session: null, trim: false, host: 'unknown', codex: false };
|
|
47
57
|
for (let i = 0; i < argv.length; i++) {
|
|
48
58
|
if (argv[i] === '--session') {
|
|
49
59
|
const value = argv[i + 1];
|
|
@@ -52,6 +62,17 @@ function parseArgs(argv) {
|
|
|
52
62
|
}
|
|
53
63
|
args.session = value;
|
|
54
64
|
i++;
|
|
65
|
+
} else if (argv[i] === '--trim') {
|
|
66
|
+
args.trim = true;
|
|
67
|
+
} else if (argv[i] === '--codex') {
|
|
68
|
+
args.codex = true;
|
|
69
|
+
} else if (argv[i] === '--host') {
|
|
70
|
+
const value = argv[i + 1];
|
|
71
|
+
if (!['claude', 'codex', 'unknown'].includes(value)) {
|
|
72
|
+
throw new Error('--host must be claude, codex, or unknown');
|
|
73
|
+
}
|
|
74
|
+
args.host = value;
|
|
75
|
+
i++;
|
|
55
76
|
}
|
|
56
77
|
}
|
|
57
78
|
return args;
|
|
@@ -266,6 +287,469 @@ function runSessionDiagnosis(prefix) {
|
|
|
266
287
|
}
|
|
267
288
|
}
|
|
268
289
|
|
|
290
|
+
function runTrimDiagnosis(
|
|
291
|
+
host,
|
|
292
|
+
env = process.env,
|
|
293
|
+
{ auditRunner = runCodexHostPrimitiveAudit } = {},
|
|
294
|
+
) {
|
|
295
|
+
const info = describeTrimHost(host);
|
|
296
|
+
const codexIdentity =
|
|
297
|
+
info.host === 'codex' ? resolveCodexThreadIdentity({ codexThreadId: null }, env) : null;
|
|
298
|
+
const hostPrimitiveDiagnosis =
|
|
299
|
+
info.host === 'codex' ? readCodexHostPrimitiveDiagnosis({ env, auditRunner }) : null;
|
|
300
|
+
console.log(`${BOLD}[Trim]${RESET}\n`);
|
|
301
|
+
console.log(` host: ${info.host}`);
|
|
302
|
+
console.log(` default keep-recent: ${DEFAULT_TRIM_KEEP_RECENT}`);
|
|
303
|
+
console.log(` automatic rollback: ${info.automaticRollback ? 'yes' : 'no'}`);
|
|
304
|
+
console.log(` automatic inject: ${info.automaticInject ? 'yes' : 'no'}`);
|
|
305
|
+
console.log(` boundary status: ${info.status}`);
|
|
306
|
+
console.log(` boundary reason: ${info.reason}`);
|
|
307
|
+
if (codexIdentity) {
|
|
308
|
+
const identityText = codexIdentity.codexThreadId
|
|
309
|
+
? `${codexIdentity.codexThreadId} (${codexIdentity.codexThreadIdSource})`
|
|
310
|
+
: 'not detected';
|
|
311
|
+
console.log(` current Codex thread: ${identityText}`);
|
|
312
|
+
}
|
|
313
|
+
if (hostPrimitiveDiagnosis) {
|
|
314
|
+
console.log(` host primitive audit: ${hostPrimitiveDiagnosis.status}`);
|
|
315
|
+
console.log(` host primitive reason: ${hostPrimitiveDiagnosis.reason}`);
|
|
316
|
+
console.log(
|
|
317
|
+
` current-thread non-resurrection: ${
|
|
318
|
+
hostPrimitiveDiagnosis.hasCurrentThreadNonResurrectionPrimitive ? 'yes' : 'no'
|
|
319
|
+
}`,
|
|
320
|
+
);
|
|
321
|
+
console.log(` repair contract: ${hostPrimitiveDiagnosis.repairContractStatus}`);
|
|
322
|
+
}
|
|
323
|
+
console.log('');
|
|
324
|
+
console.log(' dry-run command:');
|
|
325
|
+
if (info.host === 'codex' && !codexIdentity?.codexThreadId) {
|
|
326
|
+
console.log(' throughline trim --dry-run --host codex --codex-thread-id <id>');
|
|
327
|
+
console.log(' throughline trim --preflight --host codex --codex-thread-id <id>');
|
|
328
|
+
} else if (info.host === 'codex') {
|
|
329
|
+
console.log(' throughline trim --dry-run --host codex');
|
|
330
|
+
console.log(' throughline trim --preflight --host codex');
|
|
331
|
+
} else {
|
|
332
|
+
console.log(` throughline trim --dry-run --host ${info.host}`);
|
|
333
|
+
}
|
|
334
|
+
if (info.host === 'codex') {
|
|
335
|
+
console.log(' throughline trim --execute --host codex');
|
|
336
|
+
}
|
|
337
|
+
if (info.host === 'codex') {
|
|
338
|
+
const sessionId = codexIdentity?.codexThreadId
|
|
339
|
+
? `codex:${codexIdentity.codexThreadId}`
|
|
340
|
+
: 'codex:<thread-id>';
|
|
341
|
+
console.log('');
|
|
342
|
+
console.log(' fresh-thread continuation path:');
|
|
343
|
+
console.log(' status: fresh-thread-handoff-available');
|
|
344
|
+
console.log(' reason: optional_fresh_thread_continuation');
|
|
345
|
+
console.log(' safety scope: fresh_thread_handoff_no_current_thread_mutation');
|
|
346
|
+
console.log(` guided: throughline codex-handoff-start --session ${sessionId}`);
|
|
347
|
+
console.log(` smoke: throughline codex-handoff-smoke --session ${sessionId}`);
|
|
348
|
+
console.log(` model smoke dry-run: throughline codex-handoff-model-smoke --session ${sessionId} --dry-run --json`);
|
|
349
|
+
console.log(` memory: throughline codex-resume --session ${sessionId} --format handoff`);
|
|
350
|
+
console.log(' then: start a new Codex thread with that handoff context only if desired');
|
|
351
|
+
}
|
|
352
|
+
console.log('');
|
|
353
|
+
console.log(' manual procedure:');
|
|
354
|
+
for (const step of info.manualProcedure) {
|
|
355
|
+
console.log(` - ${step}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function findLatestCapturedCodexSession(db, projectPath) {
|
|
360
|
+
try {
|
|
361
|
+
return (
|
|
362
|
+
db
|
|
363
|
+
.prepare(
|
|
364
|
+
`SELECT session_id, updated_at
|
|
365
|
+
FROM sessions
|
|
366
|
+
WHERE lower(project_path) = lower(?)
|
|
367
|
+
AND session_id LIKE 'codex:%'
|
|
368
|
+
ORDER BY updated_at DESC
|
|
369
|
+
LIMIT 1`,
|
|
370
|
+
)
|
|
371
|
+
.get(projectPath) ?? null
|
|
372
|
+
);
|
|
373
|
+
} catch {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function countCapturedCodexSessions(db, projectPath) {
|
|
379
|
+
try {
|
|
380
|
+
return db
|
|
381
|
+
.prepare(
|
|
382
|
+
`SELECT COUNT(*) AS count
|
|
383
|
+
FROM sessions
|
|
384
|
+
WHERE lower(project_path) = lower(?)
|
|
385
|
+
AND session_id LIKE 'codex:%'`,
|
|
386
|
+
)
|
|
387
|
+
.get(projectPath).count;
|
|
388
|
+
} catch {
|
|
389
|
+
return 0;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function readCodexHookDiagnosis(codexHome) {
|
|
394
|
+
const hooksPath = join(codexHome, 'hooks.json');
|
|
395
|
+
const configPath = join(codexHome, 'config.toml');
|
|
396
|
+
const expectedCommand = buildCodexStopHookCommand();
|
|
397
|
+
const out = {
|
|
398
|
+
hooksPath,
|
|
399
|
+
configPath,
|
|
400
|
+
expectedCommand,
|
|
401
|
+
hooksReadable: false,
|
|
402
|
+
featureEnabled: false,
|
|
403
|
+
managedStopHooks: [],
|
|
404
|
+
legacyManagedStopHooks: [],
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
if (existsSync(configPath)) {
|
|
408
|
+
try {
|
|
409
|
+
out.featureEnabled = /^\s*codex_hooks\s*=\s*true\s*$/m.test(readFileSync(configPath, 'utf8'));
|
|
410
|
+
} catch {
|
|
411
|
+
out.featureEnabled = false;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (!existsSync(hooksPath)) return out;
|
|
416
|
+
let parsed;
|
|
417
|
+
try {
|
|
418
|
+
parsed = JSON.parse(readFileSync(hooksPath, 'utf8'));
|
|
419
|
+
} catch {
|
|
420
|
+
return out;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
out.hooksReadable = true;
|
|
424
|
+
const stopHooks = (parsed.hooks?.Stop ?? []).flatMap(group => group.hooks ?? []);
|
|
425
|
+
out.managedStopHooks = stopHooks.filter(h => isThroughlineCodexStopCommand(h.command));
|
|
426
|
+
out.legacyManagedStopHooks = out.managedStopHooks.filter(h => h.command !== expectedCommand);
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function runCodexDiagnosis({
|
|
431
|
+
env = process.env,
|
|
432
|
+
cwd = process.cwd(),
|
|
433
|
+
db = getDb(),
|
|
434
|
+
auditRunner = runCodexHostPrimitiveAudit,
|
|
435
|
+
} = {}) {
|
|
436
|
+
const codexHome = env.CODEX_HOME || defaultCodexHome();
|
|
437
|
+
const identity = resolveCodexThreadIdentity({ codexThreadId: null }, env);
|
|
438
|
+
const hookDiagnosis = readCodexHookDiagnosis(codexHome);
|
|
439
|
+
const hostPrimitiveDiagnosis = readCodexHostPrimitiveDiagnosis({ env, auditRunner });
|
|
440
|
+
const monitorTaskDiagnosis = readVsCodeMonitorTaskDiagnosis(cwd);
|
|
441
|
+
const candidates = listCodexThreadCandidates({
|
|
442
|
+
codexHome,
|
|
443
|
+
projectPath: cwd,
|
|
444
|
+
limit: 3,
|
|
445
|
+
});
|
|
446
|
+
const latestCaptured = findLatestCapturedCodexSession(db, cwd);
|
|
447
|
+
const capturedCount = countCapturedCodexSessions(db, cwd);
|
|
448
|
+
const refreshDiagnosis = buildCodexContextRefreshDiagnosis({
|
|
449
|
+
db,
|
|
450
|
+
cwd,
|
|
451
|
+
codexHome,
|
|
452
|
+
identity,
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
console.log(`${BOLD}[Codex primary]${RESET}\n`);
|
|
456
|
+
console.log(` project: ${cwd}`);
|
|
457
|
+
console.log(` CODEX_HOME: ${codexHome}`);
|
|
458
|
+
console.log(` Codex hooks feature: ${hookDiagnosis.featureEnabled ? 'enabled' : 'not enabled'}`);
|
|
459
|
+
console.log(` Codex Stop hook: ${
|
|
460
|
+
hookDiagnosis.managedStopHooks.length === 0
|
|
461
|
+
? 'not registered'
|
|
462
|
+
: hookDiagnosis.legacyManagedStopHooks.length > 0
|
|
463
|
+
? 'legacy command needs reinstall'
|
|
464
|
+
: 'registered'
|
|
465
|
+
}`);
|
|
466
|
+
if (hookDiagnosis.managedStopHooks.length > 0) {
|
|
467
|
+
const h = hookDiagnosis.managedStopHooks[0];
|
|
468
|
+
console.log(` command: ${h.command}`);
|
|
469
|
+
console.log(` async: ${h.async === false ? 'false' : String(h.async)}`);
|
|
470
|
+
console.log(` timeoutSec: ${h.timeoutSec ?? '(default)'}`);
|
|
471
|
+
}
|
|
472
|
+
console.log(` VSCode monitor task: ${monitorTaskDiagnosis.status}`);
|
|
473
|
+
if (monitorTaskDiagnosis.path) {
|
|
474
|
+
console.log(` path: ${monitorTaskDiagnosis.path}`);
|
|
475
|
+
}
|
|
476
|
+
if (monitorTaskDiagnosis.runOn) {
|
|
477
|
+
console.log(` runOn: ${monitorTaskDiagnosis.runOn}`);
|
|
478
|
+
}
|
|
479
|
+
if (monitorTaskDiagnosis.note) {
|
|
480
|
+
console.log(` note: ${monitorTaskDiagnosis.note}`);
|
|
481
|
+
}
|
|
482
|
+
console.log(
|
|
483
|
+
` current Codex thread: ${
|
|
484
|
+
identity.codexThreadId
|
|
485
|
+
? `${identity.codexThreadId} (${identity.codexThreadIdSource})`
|
|
486
|
+
: 'not detected'
|
|
487
|
+
}`,
|
|
488
|
+
);
|
|
489
|
+
console.log(` rollout candidates: ${candidates.length}`);
|
|
490
|
+
if (candidates.length > 0) {
|
|
491
|
+
const latest = candidates[0];
|
|
492
|
+
console.log(` latest rollout: ${latest.id}`);
|
|
493
|
+
console.log(` updatedAt: ${latest.updatedAt}`);
|
|
494
|
+
console.log(` path: ${latest.rolloutPath}`);
|
|
495
|
+
}
|
|
496
|
+
console.log(` captured DB sessions: ${capturedCount}`);
|
|
497
|
+
if (latestCaptured) {
|
|
498
|
+
console.log(` latest DB session: ${latestCaptured.session_id}`);
|
|
499
|
+
console.log(` updatedAt: ${formatTs(latestCaptured.updated_at)}`);
|
|
500
|
+
}
|
|
501
|
+
if (refreshDiagnosis) {
|
|
502
|
+
console.log(` context refresh: ${refreshDiagnosis.status}`);
|
|
503
|
+
if (refreshDiagnosis.blockedReason) {
|
|
504
|
+
console.log(` blocked reason: ${refreshDiagnosis.blockedReason}`);
|
|
505
|
+
}
|
|
506
|
+
console.log(` rollback source: ${refreshDiagnosis.rollbackSource}`);
|
|
507
|
+
console.log(` inject memory source: ${refreshDiagnosis.injectMemorySource}`);
|
|
508
|
+
console.log(` memory contract: ${refreshDiagnosis.memoryContract}`);
|
|
509
|
+
console.log(` L1 summaries: ${refreshDiagnosis.l1Summaries}`);
|
|
510
|
+
console.log(` recent L2 bodies: ${refreshDiagnosis.recentBodies}`);
|
|
511
|
+
console.log(` L3 references only: ${refreshDiagnosis.l3References} (bodies not injected)`);
|
|
512
|
+
if (refreshDiagnosis.handoffSmoke) {
|
|
513
|
+
console.log(` new-thread handoff: ${refreshDiagnosis.handoffSmoke.status}`);
|
|
514
|
+
if (refreshDiagnosis.safeContinuationStatus) {
|
|
515
|
+
console.log(` safe continuation: ${refreshDiagnosis.safeContinuationStatus}`);
|
|
516
|
+
}
|
|
517
|
+
console.log(` prompt chars: ${refreshDiagnosis.handoffSmoke.promptChars}`);
|
|
518
|
+
console.log(` estimated tokens: ${refreshDiagnosis.handoffSmoke.estimatedTokens}`);
|
|
519
|
+
}
|
|
520
|
+
if (refreshDiagnosis.estimate) {
|
|
521
|
+
console.log(` estimated reduction: ${refreshDiagnosis.estimate}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
console.log(` host primitive audit: ${hostPrimitiveDiagnosis.status}`);
|
|
525
|
+
console.log(` reason: ${hostPrimitiveDiagnosis.reason}`);
|
|
526
|
+
console.log(
|
|
527
|
+
` current-thread non-resurrection: ${
|
|
528
|
+
hostPrimitiveDiagnosis.hasCurrentThreadNonResurrectionPrimitive ? 'yes' : 'no'
|
|
529
|
+
}`,
|
|
530
|
+
);
|
|
531
|
+
console.log(` repair contract: ${hostPrimitiveDiagnosis.repairContractStatus}`);
|
|
532
|
+
console.log('');
|
|
533
|
+
console.log(' next commands:');
|
|
534
|
+
if (identity.codexThreadId) {
|
|
535
|
+
console.log(` throughline codex-capture --codex-thread-id ${identity.codexThreadId}`);
|
|
536
|
+
console.log(` throughline codex-handoff-start --session codex:${identity.codexThreadId}`);
|
|
537
|
+
console.log(` throughline codex-handoff-smoke --session codex:${identity.codexThreadId}`);
|
|
538
|
+
console.log(` throughline codex-handoff-model-smoke --session codex:${identity.codexThreadId} --dry-run --json`);
|
|
539
|
+
console.log(` throughline codex-resume --session codex:${identity.codexThreadId} --format handoff`);
|
|
540
|
+
console.log(` throughline codex-resume --session codex:${identity.codexThreadId}`);
|
|
541
|
+
console.log(` THROUGHLINE_EXPERIMENTAL_CODEX_HANDOFF_MODEL_SMOKE=1 throughline codex-handoff-model-smoke --session codex:${identity.codexThreadId}`);
|
|
542
|
+
} else {
|
|
543
|
+
console.log(' throughline codex-threads --limit 5');
|
|
544
|
+
console.log(' throughline codex-capture --codex-thread-id <id>');
|
|
545
|
+
console.log(' throughline codex-handoff-start --session codex:<id>');
|
|
546
|
+
console.log(' throughline codex-handoff-smoke --session codex:<id>');
|
|
547
|
+
console.log(' throughline codex-handoff-model-smoke --session codex:<id> --dry-run --json');
|
|
548
|
+
console.log(' throughline codex-resume --session codex:<id> --format handoff');
|
|
549
|
+
console.log(' throughline codex-resume --session codex:<id>');
|
|
550
|
+
console.log(' THROUGHLINE_EXPERIMENTAL_CODEX_HANDOFF_MODEL_SMOKE=1 throughline codex-handoff-model-smoke --session codex:<id>');
|
|
551
|
+
}
|
|
552
|
+
console.log(' throughline doctor --trim --host codex');
|
|
553
|
+
console.log(' throughline codex-host-primitive-audit');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function readCodexHostPrimitiveDiagnosis({
|
|
557
|
+
env = process.env,
|
|
558
|
+
auditRunner = runCodexHostPrimitiveAudit,
|
|
559
|
+
} = {}) {
|
|
560
|
+
const command = env.THROUGHLINE_CODEX_APP_SERVER_BIN ?? 'codex';
|
|
561
|
+
try {
|
|
562
|
+
const audit = auditRunner({ command });
|
|
563
|
+
return {
|
|
564
|
+
status: audit.status ?? 'unknown',
|
|
565
|
+
reason: audit.reason ?? 'unknown',
|
|
566
|
+
hasCurrentThreadRemediationPrimitive: Boolean(
|
|
567
|
+
audit.facts?.hasCurrentThreadRemediationPrimitive,
|
|
568
|
+
),
|
|
569
|
+
hasCurrentThreadNonResurrectionPrimitive: Boolean(
|
|
570
|
+
audit.facts?.hasCurrentThreadNonResurrectionPrimitive ??
|
|
571
|
+
audit.facts?.hasCurrentThreadRemediationPrimitive,
|
|
572
|
+
),
|
|
573
|
+
repairContractStatus: audit.repairContract?.status ?? 'unknown',
|
|
574
|
+
methodCount: audit.methodCount ?? null,
|
|
575
|
+
};
|
|
576
|
+
} catch (err) {
|
|
577
|
+
return {
|
|
578
|
+
status: 'unavailable',
|
|
579
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
580
|
+
hasCurrentThreadRemediationPrimitive: false,
|
|
581
|
+
hasCurrentThreadNonResurrectionPrimitive: false,
|
|
582
|
+
repairContractStatus: 'unavailable',
|
|
583
|
+
methodCount: null,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function readVsCodeMonitorTaskDiagnosis(cwd) {
|
|
589
|
+
const tasksPath = join(cwd, '.vscode', 'tasks.json');
|
|
590
|
+
if (!existsSync(tasksPath)) {
|
|
591
|
+
return {
|
|
592
|
+
status: 'not registered',
|
|
593
|
+
path: tasksPath,
|
|
594
|
+
note: 'created by the next VSCode hook event; if the folder is already open, reload VSCode once after creation',
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
let text;
|
|
599
|
+
try {
|
|
600
|
+
text = readFileSync(tasksPath, 'utf8');
|
|
601
|
+
} catch (err) {
|
|
602
|
+
return {
|
|
603
|
+
status: 'unreadable',
|
|
604
|
+
path: tasksPath,
|
|
605
|
+
note: err instanceof Error ? err.message : 'read failed',
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (detectJsoncFeatures(text)) {
|
|
610
|
+
return {
|
|
611
|
+
status: 'jsonc not inspected',
|
|
612
|
+
path: tasksPath,
|
|
613
|
+
note: 'Throughline will not auto-edit JSONC tasks; add or verify the monitor task manually',
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
let parsed;
|
|
618
|
+
try {
|
|
619
|
+
parsed = JSON.parse(text);
|
|
620
|
+
} catch {
|
|
621
|
+
return {
|
|
622
|
+
status: 'parse error',
|
|
623
|
+
path: tasksPath,
|
|
624
|
+
note: 'tasks.json is not valid JSON; Throughline will not auto-edit it',
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const index = findMonitorTaskIndex(parsed);
|
|
629
|
+
if (index < 0) {
|
|
630
|
+
return {
|
|
631
|
+
status: 'not registered',
|
|
632
|
+
path: tasksPath,
|
|
633
|
+
note: 'created by the next VSCode hook event; if the folder is already open, reload VSCode once after creation',
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const task = parsed.tasks[index];
|
|
638
|
+
if (isMonitorTaskBroken(task)) {
|
|
639
|
+
return {
|
|
640
|
+
status: 'registered but broken',
|
|
641
|
+
path: tasksPath,
|
|
642
|
+
runOn: task?.runOptions?.runOn ?? '(missing)',
|
|
643
|
+
note: 'existing task points at a missing absolute path; the next VSCode hook event should repair it',
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
status: 'registered',
|
|
649
|
+
path: tasksPath,
|
|
650
|
+
runOn: task?.runOptions?.runOn ?? '(missing)',
|
|
651
|
+
note: 'if it was created after this folder was already open, run Developer: Reload Window once or start the Throughline Monitor task manually',
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function buildCodexContextRefreshDiagnosis({ db, cwd, codexHome, identity }) {
|
|
656
|
+
if (!identity.codexThreadId) return null;
|
|
657
|
+
|
|
658
|
+
let trimSource = null;
|
|
659
|
+
try {
|
|
660
|
+
trimSource = buildCodexRolloutTrimSource({
|
|
661
|
+
threadId: identity.codexThreadId,
|
|
662
|
+
codexHome,
|
|
663
|
+
projectPath: cwd,
|
|
664
|
+
sourceReason:
|
|
665
|
+
identity.codexThreadIdSource && identity.codexThreadIdSource.startsWith('env:')
|
|
666
|
+
? 'env_codex_thread_rollout'
|
|
667
|
+
: 'explicit_codex_thread_rollout',
|
|
668
|
+
});
|
|
669
|
+
} catch {
|
|
670
|
+
trimSource = null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
let plan;
|
|
674
|
+
try {
|
|
675
|
+
plan = buildTrimPlan(db, {
|
|
676
|
+
projectPath: cwd,
|
|
677
|
+
host: 'codex',
|
|
678
|
+
trimAll: true,
|
|
679
|
+
codexThreadId: identity.codexThreadId,
|
|
680
|
+
codexThreadIdSource: identity.codexThreadIdSource,
|
|
681
|
+
trimSource,
|
|
682
|
+
});
|
|
683
|
+
} catch {
|
|
684
|
+
return {
|
|
685
|
+
status: 'unavailable',
|
|
686
|
+
rollbackSource: trimSource?.source ?? 'unknown',
|
|
687
|
+
injectMemorySource: 'unknown',
|
|
688
|
+
memoryContract: 'unavailable',
|
|
689
|
+
l1Summaries: 'unknown',
|
|
690
|
+
recentBodies: 'unknown',
|
|
691
|
+
l3References: 'unknown',
|
|
692
|
+
estimate: null,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const stats = plan.memoryPreview?.stats ?? {};
|
|
697
|
+
const hasDbMemory =
|
|
698
|
+
stats.source === 'throughline-db' &&
|
|
699
|
+
((stats.l1Summaries ?? 0) > 0 || (stats.recentBodies ?? 0) > 0 || (stats.l3References ?? 0) > 0);
|
|
700
|
+
let handoffSmoke = null;
|
|
701
|
+
if (hasDbMemory) {
|
|
702
|
+
try {
|
|
703
|
+
const record = buildHandoffRecord(db, {
|
|
704
|
+
sessionId: `codex:${identity.codexThreadId}`,
|
|
705
|
+
isInheritance: false,
|
|
706
|
+
});
|
|
707
|
+
if (record) {
|
|
708
|
+
const smoke = buildCodexHandoffSmoke(record);
|
|
709
|
+
handoffSmoke = {
|
|
710
|
+
status: smoke.status,
|
|
711
|
+
reason: smoke.reason,
|
|
712
|
+
promptChars: smoke.promptChars,
|
|
713
|
+
estimatedTokens: smoke.estimatedTokens,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
} catch {
|
|
717
|
+
handoffSmoke = {
|
|
718
|
+
status: 'unavailable',
|
|
719
|
+
reason: 'handoff_smoke_failed',
|
|
720
|
+
promptChars: 'unknown',
|
|
721
|
+
estimatedTokens: 'unknown',
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const estimate = plan.trim?.contextReductionEstimate;
|
|
726
|
+
return {
|
|
727
|
+
status: hasDbMemory ? 'ready' : 'not ready',
|
|
728
|
+
blockedReason: null,
|
|
729
|
+
rollbackSource: plan.trim?.source ?? 'unknown',
|
|
730
|
+
injectMemorySource: stats.source ?? 'unknown',
|
|
731
|
+
memoryContract: hasDbMemory
|
|
732
|
+
? 'older L1 + latest 20 L2 full bodies + L3 references only'
|
|
733
|
+
: 'Throughline DB memory required; rollout preview is not injected',
|
|
734
|
+
l1Summaries: stats.l1Summaries ?? 0,
|
|
735
|
+
recentBodies:
|
|
736
|
+
typeof stats.recentBodies === 'number'
|
|
737
|
+
? `${stats.recentBodies} rows (latest ${stats.recentTurnLimit ?? DEFAULT_TRIM_KEEP_RECENT} turns)`
|
|
738
|
+
: 'unknown',
|
|
739
|
+
l3References: stats.l3References ?? 0,
|
|
740
|
+
handoffSmoke,
|
|
741
|
+
safeContinuationStatus:
|
|
742
|
+
handoffSmoke?.status === 'ready'
|
|
743
|
+
? 'fresh-thread-handoff-available'
|
|
744
|
+
: handoffSmoke
|
|
745
|
+
? 'handoff-not-ready'
|
|
746
|
+
: null,
|
|
747
|
+
estimate: estimate
|
|
748
|
+
? `${estimate.netEstimatedTokens} tokens (${estimate.reductionPct}%, ${estimate.method})`
|
|
749
|
+
: null,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
269
753
|
export async function run(argv = []) {
|
|
270
754
|
let args;
|
|
271
755
|
try {
|
|
@@ -280,6 +764,16 @@ export async function run(argv = []) {
|
|
|
280
764
|
return;
|
|
281
765
|
}
|
|
282
766
|
|
|
767
|
+
if (args.trim) {
|
|
768
|
+
runTrimDiagnosis(args.host);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (args.codex) {
|
|
773
|
+
runCodexDiagnosis();
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
|
|
283
777
|
console.log('throughline doctor\n');
|
|
284
778
|
|
|
285
779
|
// Node.js バージョン
|
|
@@ -343,6 +837,8 @@ export async function run(argv = []) {
|
|
|
343
837
|
|
|
344
838
|
console.log('');
|
|
345
839
|
console.log(`${DIM}ヒント: 特定セッションが止まって見えるときは ${RESET}throughline doctor --session <id-prefix>${DIM} で診断できます。${RESET}`);
|
|
840
|
+
console.log(`${DIM}ヒント: trim の host 境界を見るには ${RESET}throughline doctor --trim --host claude${DIM} を使います。${RESET}`);
|
|
841
|
+
console.log(`${DIM}ヒント: Codex primary の入口を見るには ${RESET}throughline doctor --codex${DIM} を使います。${RESET}`);
|
|
346
842
|
}
|
|
347
843
|
|
|
348
844
|
// テスト用エクスポート
|
|
@@ -351,6 +847,12 @@ export const _internal = {
|
|
|
351
847
|
formatAgo,
|
|
352
848
|
formatBytes,
|
|
353
849
|
runSessionDiagnosis,
|
|
850
|
+
runTrimDiagnosis,
|
|
851
|
+
runCodexDiagnosis,
|
|
852
|
+
buildCodexContextRefreshDiagnosis,
|
|
853
|
+
readCodexHostPrimitiveDiagnosis,
|
|
854
|
+
readCodexHookDiagnosis,
|
|
855
|
+
readVsCodeMonitorTaskDiagnosis,
|
|
354
856
|
isPidAlive,
|
|
355
857
|
findLatestJsonlInSameDir,
|
|
356
858
|
};
|