mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 18:39:33 +01:00
A forked or spawned thread's rollout carries its own session_meta followed by its parent's and a copy of the parent's history. A later session_meta for a declared ancestor now opens an inherited prefix that is dropped: by ordinal below subagent_history_start_ordinal in paginated history, and up to the thread_settings_applied event Codex appends after the copy in legacy history, checked against the parent's own messages when the copy holds more than one candidate. An unrelated id still refuses the file. Store attribution now has its own budget (--metadata-bytes) separate from the transcript budget (--scan-bytes), reads each head in 32 KiB steps, and --max-entries is a flag, so id lookups work on stores of thousands of rollouts. An unattributable store file or unreadable directory is counted instead of aborting the lookup, and a rollout that a running Codex appends to is read as a snapshot up to its last complete line. User records carry no phase, date bounds are UTC calendar dates, the injected-context rule is the shared one, a symlinked sessions root is followed as the Past Sessions scanner follows it, and --json with --format text is refused.
96 lines
4.3 KiB
TypeScript
96 lines
4.3 KiB
TypeScript
/**
|
|
* @fileoverview `codeman history read`: the command-line front of the offline
|
|
* Codex history reader (`local-history.ts`).
|
|
*
|
|
* Deliberately separate from `codeman agent` (`cli-agent.ts`): that command is
|
|
* a client of a running Codeman and refuses outside a managed session, while
|
|
* this one reads rollout files from disk and needs neither a server nor
|
|
* `CODEMAN_MUX`. It never writes, never inspects a live pane and never sends
|
|
* input.
|
|
*
|
|
* Every bound the reader enforces has a flag here with the reader's own
|
|
* maximum, so a large store is a matter of raising a number rather than an
|
|
* impossible lookup. Output is JSON unless `--format text` is given; `--json`
|
|
* is the JSON spelling and refuses to combine with `--format text`.
|
|
*
|
|
* @module cli-history
|
|
*/
|
|
import type { Command } from 'commander';
|
|
import {
|
|
formatLocalHistory,
|
|
HISTORY_DEFAULT_MAX_ENTRIES,
|
|
HISTORY_DEFAULT_METADATA_BYTES,
|
|
HISTORY_DEFAULT_OUTPUT_BYTES,
|
|
HISTORY_DEFAULT_SCAN_BYTES,
|
|
HISTORY_MAX_ENTRIES,
|
|
HISTORY_MAX_METADATA_BYTES,
|
|
HISTORY_MAX_OUTPUT_BYTES,
|
|
HISTORY_MAX_SCAN_BYTES,
|
|
readLocalHistory,
|
|
} from './local-history.js';
|
|
|
|
export function registerHistoryCommands(program: Command): Command {
|
|
const history = program
|
|
.command('history')
|
|
.description('Read local saved native transcripts without a running server');
|
|
history
|
|
.command('read')
|
|
.description('Read Codex history by file, native id, or root conversation and descendants')
|
|
.option('--file <path>', 'Explicit local JSONL transcript (regular file only)')
|
|
.option('--session-id <id>', 'Native Codex conversation UUID or unique prefix (at least 8 characters)')
|
|
.option('--root-session-id <uuid>', 'Native root UUID, including transitive child conversations')
|
|
.option(
|
|
'--sessions-root <path>',
|
|
'Local Codex sessions directory (default: $CODEX_HOME/sessions, else ~/.codex/sessions)'
|
|
)
|
|
.option('--topic <text>', 'Case-insensitive literal match, preserving whole matching turns')
|
|
.option('--after <date>', 'Inclusive YYYY-MM-DD lower bound, as a UTC date; preserves whole matching turns')
|
|
.option('--before <date>', 'Inclusive YYYY-MM-DD upper bound, as a UTC date; preserves whole matching turns')
|
|
.option(
|
|
'--max-bytes <n>',
|
|
`Maximum complete output bytes (1-${HISTORY_MAX_OUTPUT_BYTES})`,
|
|
String(HISTORY_DEFAULT_OUTPUT_BYTES)
|
|
)
|
|
.option(
|
|
'--scan-bytes <n>',
|
|
`Read budget for the selected transcripts (1-${HISTORY_MAX_SCAN_BYTES})`,
|
|
String(HISTORY_DEFAULT_SCAN_BYTES)
|
|
)
|
|
.option(
|
|
'--metadata-bytes <n>',
|
|
`Read budget for attributing every rollout in the store (1-${HISTORY_MAX_METADATA_BYTES})`,
|
|
String(HISTORY_DEFAULT_METADATA_BYTES)
|
|
)
|
|
.option(
|
|
'--max-entries <n>',
|
|
`Directory entries visited in the store (1-${HISTORY_MAX_ENTRIES})`,
|
|
String(HISTORY_DEFAULT_MAX_ENTRIES)
|
|
)
|
|
.option('--format <format>', 'json or text', 'json')
|
|
.option('--json', 'JSON output (the default; conflicts with --format text)')
|
|
.action(async (options: Record<string, string | boolean | undefined>, command: Command) => {
|
|
try {
|
|
if (options.format !== 'json' && options.format !== 'text') throw new Error('--format must be json or text');
|
|
if (options.json && options.format === 'text' && command.getOptionValueSource('format') === 'cli')
|
|
throw new Error('--json conflicts with --format text; pass one of them');
|
|
const format = options.format;
|
|
const result = await readLocalHistory({
|
|
file: options.file as string | undefined,
|
|
sessionId: options.sessionId as string | undefined,
|
|
rootSessionId: options.rootSessionId as string | undefined,
|
|
sessionsRoot: options.sessionsRoot as string | undefined,
|
|
topic: options.topic as string | undefined,
|
|
after: options.after as string | undefined,
|
|
before: options.before as string | undefined,
|
|
scanBytes: Number(options.scanBytes),
|
|
metadataBytes: Number(options.metadataBytes),
|
|
maxEntries: Number(options.maxEntries),
|
|
});
|
|
program.configureOutput().writeOut?.(`${formatLocalHistory(result, format, Number(options.maxBytes))}\n`);
|
|
} catch (error) {
|
|
program.error(error instanceof Error ? error.message : String(error), { exitCode: 1, code: 'history.read' });
|
|
}
|
|
});
|
|
return history;
|
|
}
|