1use tau_config::settings as path_tau_config_settings;
8
9pub mod cli;
10
11mod action_commands;
12mod agent_activity;
13mod agent_navigation;
14mod chat;
15mod daemon;
16mod dev_tmux;
17mod estimated_cost;
18mod event_renderer;
19mod kill_session;
20mod line_output;
21mod list_agents;
22mod list_sessions;
23mod markdown_render;
24mod message_fact_render;
25mod papercut;
26mod peer_exit;
27mod preview_declarations;
28mod print_prompt;
29mod print_skills;
30mod print_tools;
31mod prompt_history;
32mod prompt_stdin;
33mod provider_quota;
34mod render_request;
35mod renderer_handle;
36mod send;
37mod settings_registry;
38mod skill_commands;
39mod terminal_text;
40mod theme;
41mod tool_render;
42mod transcript_markers;
43mod turn_stats_projection;
44mod ui_client;
45mod ui_commands;
46mod ui_events;
47mod ui_logging;
48mod ui_prompt;
49mod unload_agent;
50mod watch_activity;
51
52use std::process::ExitCode;
53use std::sync::{Mutex, MutexGuard};
54use std::{fmt, io};
55
56use tau_harness::SessionLaunchStatus;
57
58use crate::chat::run_chat;
59use crate::daemon::resolve_resume_session_id;
60
61pub(crate) const MUTEX_POISONED: &str = "mutex poisoned";
65
66pub(crate) fn locked<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
70 mutex.lock().expect(MUTEX_POISONED)
71}
72
73mod built_info;
74
75const STARTUP_PUNS: &[&str] = &[
76 "Tau is like Pi, but twice as much.",
77 "A whole new angle on coding agents.",
78 "Tau day is every day if you care about circles enough.",
79 "Come for the agent, stay for the circumference discourse.",
80 "Tau is the irrational choice for rational Unix hackers.",
81 "Small tools, loosely joined — that’s the Tau of Unix.",
82 "In Tau, what goes around comes around over stdio.",
83 "We’ve come full τurn.",
84 "Tau keeps the loop tight and the pipes honest.",
85 "Every extension gets its turn in Tau.",
86 "Tau speaks fluent stdio with a circular accent.",
87 "Agents, tools, sockets, loops: a well-rounded lineup.",
88 "Ready, set, Tau!",
89 "Tau day to code.",
90 "Tau-tau control.",
91 "Tau-tally operational.",
92 "Tau much power in one terminal.",
93 "Tau infinity and beyond.",
94 "Tau the line between human and agent.",
95 "Tau’s what I’m talking about.",
96 "One shell to Tau them all.",
97 "Tau-powered, Unix-native.",
98 "Complete revolution.",
99 "Wrapping around nicely.",
100 "Continuous on S¹, probably.",
101 "Cohomology remains left as exercise.",
102];
103
104pub(crate) fn random_startup_pun() -> &'static str {
105 use rand::Rng;
106 let idx = rand::thread_rng().gen_range(0..STARTUP_PUNS.len());
107 STARTUP_PUNS[idx]
108}
109
110#[derive(Debug)]
116pub enum CliError {
117 CachePartial,
119 CacheInvalid,
121 Io(io::Error),
122 Encode(tau_proto::EncodeError),
123 Harness(tau_harness::HarnessError),
124 Inspect(tau_session_inspect::InspectError),
125 DaemonExited(String),
126 NoRunningDaemon,
127 Participant(String),
128 PromptStdin(PromptStdinError),
129 SessionNotFound(String),
130 ForegroundOwnershipUnconfirmed {
133 message: String,
135 diagnostic: tau_cli_term::ForegroundRestorationDiagnostic,
137 },
138 TerminalOutputFailed(String),
140}
141
142impl fmt::Display for CliError {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 match self {
145 Self::CachePartial => f.write_str("cache analysis is partial; see report coverage"),
146 Self::CacheInvalid => f.write_str("cache source unavailable, unsupported, or invalid; use a matching-build inspector and check the selected scope and limits"),
147 Self::Io(source) => write!(f, "I/O error: {source}"),
148 Self::Encode(source) => write!(f, "encode error: {source}"),
149 Self::Harness(source) => write!(f, "harness error: {source}"),
150 Self::Inspect(source) => write!(f, "inspect error: {source}"),
151 Self::DaemonExited(msg) => write!(f, "harness daemon exited: {msg}"),
152 Self::NoRunningDaemon => {
153 f.write_str("no running session is available; start one with `tau`")
154 }
155 Self::Participant(msg) => write!(f, "participant error: {msg}"),
156 Self::PromptStdin(error) => error.fmt(f),
157 Self::SessionNotFound(id) => write!(f, "session not found: `{id}`"),
158 Self::ForegroundOwnershipUnconfirmed { message, .. } => f.write_str(message),
159 Self::TerminalOutputFailed(message) => f.write_str(message),
160 }
161 }
162}
163
164impl CliError {
165 fn exit_code(&self) -> ExitCode {
168 match self {
169 Self::CachePartial => ExitCode::from(3),
170 Self::CacheInvalid => ExitCode::from(2),
171 _ => ExitCode::FAILURE,
172 }
173 }
174
175 fn should_report_to_terminal(&self) -> bool {
177 !matches!(
178 self,
179 Self::ForegroundOwnershipUnconfirmed { .. } | Self::TerminalOutputFailed(_)
180 )
181 }
182}
183
184#[derive(Debug)]
186pub enum PromptStdinError {
187 AdmissionTimeout {
189 timeout: std::time::Duration,
191 },
192 Rejected {
194 reason: tau_proto::UiCreateAgentRejection,
196 message: String,
198 },
199 PromptFailed {
201 stage: tau_proto::AgentPromptFailureStage,
203 message: String,
205 },
206 ExecutionFailed {
208 message: String,
210 },
211}
212
213impl fmt::Display for PromptStdinError {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 match self {
216 Self::AdmissionTimeout { timeout } => write!(
217 f,
218 "timed out after {}s waiting for create-agent admission",
219 timeout.as_secs()
220 ),
221 Self::Rejected { reason, message } => {
222 write!(f, "create-agent request failed ({}): {message}", reason)
223 }
224 Self::PromptFailed { stage, message } => {
225 write!(f, "initial prompt failed ({}): {message}", stage)
226 }
227 Self::ExecutionFailed { message } => {
228 write!(f, "initial prompt failed (execution): {message}")
229 }
230 }
231 }
232}
233
234impl std::error::Error for PromptStdinError {}
235impl std::error::Error for CliError {}
236
237impl From<io::Error> for CliError {
238 fn from(source: io::Error) -> Self {
239 Self::Io(source)
240 }
241}
242
243impl From<tau_harness::HarnessError> for CliError {
244 fn from(source: tau_harness::HarnessError) -> Self {
245 Self::Harness(source)
246 }
247}
248
249impl From<tau_session_inspect::InspectError> for CliError {
250 fn from(source: tau_session_inspect::InspectError) -> Self {
251 Self::Inspect(source)
252 }
253}
254
255fn run_harness_component_default() -> Result<(), Box<dyn std::error::Error>> {
261 run_harness_component(false)
262}
263
264fn run_harness_component(initial_ui_stdio: bool) -> Result<(), Box<dyn std::error::Error>> {
265 if initial_ui_stdio {
266 tau_harness::run_component_with_internal_tools_and_initial_ui_stdio(
267 tau_harness_tools::builtin_handlers(),
268 )
269 } else {
270 tau_harness::run_component_with_internal_tools(tau_harness_tools::builtin_handlers())
271 }
272}
273
274fn build_revision() -> String {
275 tau_harness::version::build_revision()
276}
277
278fn build_last_modified() -> Option<String> {
279 tau_harness::version::build_last_modified()
284 .or_else(|| short_built_time(built_info::BUILT_TIME_UTC))
285 .filter(|date| date != "1980-01-01 00:00")
286}
287
288fn short_built_time(time: &str) -> Option<String> {
289 let input_format = time::macros::format_description!(
290 "[weekday repr:short], [day padding:none] [month repr:short] [year] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"
291 );
292 let output_format = time::macros::format_description!("[year]-[month]-[day] [hour]:[minute]");
293 time::OffsetDateTime::parse(time, input_format)
294 .ok()?
295 .format(output_format)
296 .ok()
297}
298
299pub(crate) fn build_label_parts() -> (String, String) {
300 let version = format!("tau {}", env!("CARGO_PKG_VERSION"));
301 let build = match build_last_modified() {
302 Some(date) => format!("({}, {})", build_revision(), date),
303 None => format!("({})", build_revision()),
304 };
305 (version, build)
306}
307
308fn version_label() -> String {
309 let (version, build) = build_label_parts();
310 format!("{version} {build}")
311}
312
313pub(crate) fn build_banner(theme: &tau_themes::Theme) -> tau_cli_term::StyledText {
316 use tau_themes::names;
317 let logo = tau_cli_term::resolve::resolve(theme, names::BANNER_LOGO);
318 let name = tau_cli_term::resolve::resolve(theme, names::BANNER_NAME);
319 let version_style = tau_cli_term::resolve::resolve(theme, names::BANNER_VERSION);
320 let build_style = tau_cli_term::resolve::resolve(theme, names::BANNER_BUILD);
321 let pun_style = tau_cli_term::resolve::resolve(theme, names::BANNER_PUN);
322 let pun = random_startup_pun();
323 let (version, build) = build_label_parts();
324 tau_cli_term::StyledText::from(vec![
325 tau_cli_term::Span::new("▝▜▛▀ ", logo),
326 tau_cli_term::Span::new("tau", name),
327 tau_cli_term::Span::new(version.trim_start_matches("tau"), version_style),
328 tau_cli_term::Span::new(" ", Default::default()),
329 tau_cli_term::Span::new(build, build_style),
330 tau_cli_term::Span::new("\n", Default::default()),
331 tau_cli_term::Span::new(" ▐▙▖ ", logo),
332 tau_cli_term::Span::new(pun, pun_style),
333 ])
334}
335
336pub(crate) fn mint_short_id(prefix: &str) -> String {
343 use rand::distributions::Distribution;
344
345 struct Base36;
346 impl Distribution<char> for Base36 {
347 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> char {
348 let n: u8 = rng.gen_range(0..36);
349 if n < 10 {
350 (b'0' + n) as char
351 } else {
352 (b'a' + (n - 10)) as char
353 }
354 }
355 }
356
357 let suffix: String = Base36.sample_iter(rand::thread_rng()).take(6).collect();
358 format!("{prefix}-{suffix}")
359}
360
361const SAMPLE_CLI: &str = include_str!("../release-resources/cli.yaml");
366const SAMPLE_HARNESS: &str = include_str!("../release-resources/harness.yaml");
367
368fn run_init(force: bool) -> Result<(), CliError> {
369 let Some(dir) = tau_config::settings::config_dir() else {
370 return Err(CliError::Io(io::Error::new(
371 io::ErrorKind::NotFound,
372 "could not determine config directory",
373 )));
374 };
375 std::fs::create_dir_all(&dir)?;
376
377 let files = [("cli.yaml", SAMPLE_CLI), ("harness.yaml", SAMPLE_HARNESS)];
378
379 for (name, content) in &files {
380 let path = dir.join(name);
381 if path.exists() && !force {
382 eprintln!(
383 "skip: {} (exists, use --force to overwrite)",
384 path.display()
385 );
386 } else {
387 std::fs::write(&path, content)?;
388 eprintln!("wrote: {}", path.display());
389 }
390 }
391
392 eprintln!("next: use `tau provider add` to log in to a hosted LLM provider");
393
394 Ok(())
395}
396
397pub type ComponentRunner = fn() -> Result<(), Box<dyn std::error::Error>>;
402
403fn parse_role_cli_overrides<I, S>(args: I) -> Vec<tau_config::settings::RoleCliOverride>
404where
405 I: IntoIterator<Item = S>,
406 S: Into<std::ffi::OsString>,
407{
408 let mut overrides = Vec::new();
409 let mut args = args.into_iter().map(Into::into);
410 let _program = args.next();
411 while let Some(arg) = args.next() {
412 let arg = arg.to_string_lossy();
413 if arg == "--" {
414 break;
415 }
416 if arg == "--disable-roles-all" {
417 overrides.push(path_tau_config_settings::RoleCliOverride::DisableAll);
418 } else if let Some(role) = arg.strip_prefix("--enable-role=") {
419 overrides.push(path_tau_config_settings::RoleCliOverride::Enable(
420 role.to_owned(),
421 ));
422 } else if arg == "--enable-role" {
423 if let Some(role) = args.next() {
424 overrides.push(path_tau_config_settings::RoleCliOverride::Enable(
425 role.to_string_lossy().into_owned(),
426 ));
427 }
428 } else if let Some(role) = arg.strip_prefix("--disable-role=") {
429 overrides.push(path_tau_config_settings::RoleCliOverride::Disable(
430 role.to_owned(),
431 ));
432 } else if arg == "--disable-role"
433 && let Some(role) = args.next()
434 {
435 overrides.push(path_tau_config_settings::RoleCliOverride::Disable(
436 role.to_string_lossy().into_owned(),
437 ));
438 }
439 }
440 overrides
441}
442
443fn reject_harness_config_overrides(
444 overrides: &[tau_config::settings::HarnessConfigCliOverride],
445 command: &str,
446) -> Result<(), CliError> {
447 if overrides.is_empty() {
448 return Ok(());
449 }
450 Err(CliError::Participant(format!(
451 "--harness-config can only be used when starting a new harness instance; `{command}` cannot apply it to an existing or absent harness"
452 )))
453}
454
455#[derive(Clone, Copy, Default)]
457struct ModelReferenceAliasInputPresence {
458 provider_flag: bool,
460 model_flag: bool,
462 provider_environment: bool,
464 model_environment: bool,
466}
467
468fn reject_model_reference_alias_inputs(
471 presence: ModelReferenceAliasInputPresence,
472 command: &str,
473) -> Result<(), CliError> {
474 let source = if presence.provider_flag {
475 "--provider-alias"
476 } else if presence.model_flag {
477 "--model-alias"
478 } else if presence.provider_environment {
479 tau_config::settings::TAU_PROVIDER_ALIASES_ENV
480 } else if presence.model_environment {
481 tau_config::settings::TAU_MODEL_ALIASES_ENV
482 } else {
483 return Ok(());
484 };
485 Err(CliError::Participant(format!(
486 "{source} can only be used when starting a new harness instance; `{command}` cannot apply it to an existing or absent harness"
487 )))
488}
489
490fn validate_agent_trace_mode(
491 format: cli::AgentTraceFormat,
492 mode: cli::AgentTraceMode,
493) -> Result<(), CliError> {
494 if mode == cli::AgentTraceMode::Full
495 && !matches!(
496 format,
497 cli::AgentTraceFormat::AgentToolsJsonl | cli::AgentTraceFormat::AgentToolsToon
498 )
499 {
500 return Err(CliError::Participant(
501 "`agent trace --mode full` requires `--format agent-tools-jsonl` or \
502 `--format agent-tools-toon`"
503 .to_owned(),
504 ));
505 }
506 Ok(())
507}
508
509fn reject_dev_tmux_startup_overrides(
510 profile: Option<&str>,
511 startup_role: Option<&str>,
512 role_cli_overrides: &[tau_config::settings::RoleCliOverride],
513 extension_cli_overrides: &[tau_config::settings::ExtensionCliOverride],
514 harness_config_overrides: &[tau_config::settings::HarnessConfigCliOverride],
515) -> Result<(), CliError> {
516 if profile.is_some() {
517 return Err(CliError::Participant(
518 "`tau dev tmux` cannot use a configuration profile because the outer helper must not load normal user harness config before spawning the scratch Tau".to_owned(),
519 ));
520 }
521 if startup_role.is_some() {
522 return Err(CliError::Participant(
523 "`tau dev tmux` cannot use --role because the outer helper must not load normal user harness config before spawning the scratch Tau".to_owned(),
524 ));
525 }
526 if !role_cli_overrides.is_empty() {
527 return Err(CliError::Participant(
528 "`tau dev tmux` cannot use role enable/disable overrides because the outer helper must not load normal user harness config before spawning the scratch Tau".to_owned(),
529 ));
530 }
531 if !extension_cli_overrides.is_empty() {
532 return Err(CliError::Participant(
533 "`tau dev tmux` cannot use extension enable/disable overrides because the outer helper must not load normal user harness config before spawning the scratch Tau".to_owned(),
534 ));
535 }
536 reject_harness_config_overrides(harness_config_overrides, "dev tmux")
537}
538
539fn reject_legacy_config_path(config: Option<&std::path::Path>) -> Result<(), CliError> {
540 if let Some(path) = config {
541 return Err(CliError::Participant(format!(
542 "--config is no longer supported (got `{}`); use harness.yaml or --harness-config KEY=VALUE overrides",
543 path.display()
544 )));
545 }
546 Ok(())
547}
548
549fn reject_attach_startup_overrides(
550 prompt_stdin: bool,
551 profile_selected: bool,
552 startup_role: Option<&str>,
553 role_cli_overrides: &[tau_config::settings::RoleCliOverride],
554 extension_cli_overrides: &[tau_config::settings::ExtensionCliOverride],
555) -> Result<(), CliError> {
556 if profile_selected {
557 return Err(CliError::Participant(
558 "`tau attach` cannot apply --profile to an already-running daemon".to_owned(),
559 ));
560 }
561 if startup_role.is_some() && !prompt_stdin {
562 return Err(CliError::Participant(
563 "`tau attach` cannot apply --role to an already-running interactive daemon; use --prompt-stdin if you want --role for the submitted prompt".to_owned(),
564 ));
565 }
566 if !role_cli_overrides.is_empty() {
567 return Err(CliError::Participant(
568 "`tau attach` cannot apply role enable/disable overrides to an already-running daemon"
569 .to_owned(),
570 ));
571 }
572 if !extension_cli_overrides.is_empty() {
573 return Err(CliError::Participant(
574 "`tau attach` cannot apply extension enable/disable overrides to an already-running daemon"
575 .to_owned(),
576 ));
577 }
578 Ok(())
579}
580
581fn reject_ephemeral_incompatible(
582 ephemeral: bool,
583 startup_mode: &StartupMode,
584) -> Result<(), CliError> {
585 if ephemeral {
586 let command = match startup_mode {
587 StartupMode::New => return Ok(()),
588 StartupMode::Attach(_) => "attach",
589 StartupMode::Resume(_) => "resume",
590 };
591 return Err(CliError::Participant(format!(
592 "--ephemeral cannot be combined with `tau {command}`"
593 )));
594 }
595 Ok(())
596}
597
598fn required_harness_role<'a>(role: Option<&'a str>, command: &str) -> Result<&'a str, CliError> {
599 role.ok_or_else(|| CliError::Participant(format!("tau dev {command} requires --role <role>")))
600}
601
602fn parse_extension_cli_overrides<I, S>(args: I) -> Vec<tau_config::settings::ExtensionCliOverride>
603where
604 I: IntoIterator<Item = S>,
605 S: Into<std::ffi::OsString>,
606{
607 let mut overrides = Vec::new();
608 let mut args = args.into_iter().map(Into::into);
609 let _program = args.next();
610 while let Some(arg) = args.next() {
611 let arg = arg.to_string_lossy();
612 if arg == "--" {
613 break;
614 }
615 if arg == "--enable-extensions-all" {
616 overrides.push(path_tau_config_settings::ExtensionCliOverride::EnableAll);
617 } else if arg == "--disable-extensions-all" {
618 overrides.push(path_tau_config_settings::ExtensionCliOverride::DisableAll);
619 } else if let Some(extension) = arg.strip_prefix("--enable-extension=") {
620 overrides.push(path_tau_config_settings::ExtensionCliOverride::Enable(
621 extension.to_owned(),
622 ));
623 } else if arg == "--enable-extension" {
624 if let Some(extension) = args.next() {
625 overrides.push(path_tau_config_settings::ExtensionCliOverride::Enable(
626 extension.to_string_lossy().into_owned(),
627 ));
628 }
629 } else if let Some(extension) = arg.strip_prefix("--disable-extension=") {
630 overrides.push(path_tau_config_settings::ExtensionCliOverride::Disable(
631 extension.to_owned(),
632 ));
633 } else if arg == "--disable-extension"
634 && let Some(extension) = args.next()
635 {
636 overrides.push(path_tau_config_settings::ExtensionCliOverride::Disable(
637 extension.to_string_lossy().into_owned(),
638 ));
639 }
640 }
641 overrides
642}
643
644fn parse_harness_config_cli_overrides<I, S>(
645 args: I,
646) -> Result<Vec<tau_config::settings::HarnessConfigCliOverride>, String>
647where
648 I: IntoIterator<Item = S>,
649 S: Into<std::ffi::OsString>,
650{
651 let mut overrides = Vec::new();
652 let mut args = args.into_iter().map(Into::into);
653 let _program = args.next();
654 for arg in args {
655 let arg = arg.to_string_lossy();
656 if arg == "--" {
657 break;
658 }
659 if let Some(value) = arg.strip_prefix("--harness-config=") {
660 overrides.push(value.parse()?);
661 }
662 }
663 Ok(overrides)
664}
665
666#[derive(Clone, Copy, Debug, Eq, PartialEq)]
668pub enum ComponentLogging {
669 CliStderr,
671 RunnerManaged,
674}
675
676#[derive(Clone, Debug, Eq, PartialEq)]
678enum StartupMode {
679 New,
681 Attach(Option<tau_proto::SessionId>),
683 Resume(Option<tau_proto::SessionId>),
685}
686
687enum DispatchCommand {
689 Startup {
690 args: cli::RunArgs,
691 mode: StartupMode,
692 },
693 Other(cli::Command),
694}
695
696fn consumes_harness_settings(command: &DispatchCommand) -> bool {
702 match command {
703 DispatchCommand::Startup {
704 mode: StartupMode::Attach(_),
705 ..
706 } => false,
707 DispatchCommand::Startup { .. } => true,
708 DispatchCommand::Other(cli::Command::Dev {
709 command:
710 cli::DevCommand::DumpInitialPrompt { .. }
711 | cli::DevCommand::PrintPrompt { .. }
712 | cli::DevCommand::PrintSystemPrompt
713 | cli::DevCommand::PrintSkills { .. }
714 | cli::DevCommand::PrintTools
715 | cli::DevCommand::PreviewDeclarations,
716 }) => true,
717 DispatchCommand::Other(cli::Command::Component { name, .. }) => name == "harness",
718 DispatchCommand::Other(cli::Command::Serve { .. }) => true,
719 _ => false,
720 }
721}
722
723fn rejects_model_reference_alias_inputs(command: &DispatchCommand) -> bool {
726 !consumes_harness_settings(command)
727 || matches!(
728 command,
729 DispatchCommand::Other(cli::Command::Dev {
730 command: cli::DevCommand::DumpInitialPrompt { .. }
731 })
732 )
733}
734
735pub struct Component {
736 pub name: &'static str,
738 pub runner: ComponentRunner,
740 pub logging: ComponentLogging,
742}
743
744pub fn main_with_args() -> std::process::ExitCode {
747 main_with_args_and_components(&[])
748}
749
750pub fn main_with_args_and_components(components: &[Component]) -> std::process::ExitCode {
754 let build_revision = build_revision();
755 tau_client::initialize_diagnostic_build_revision(build_revision.clone());
756 tau_ext_provider_builtin::initialize_cache_diagnostic_build(build_revision);
757 use std::process::ExitCode;
758
759 use clap::{CommandFactory, FromArgMatches};
760
761 let run = || -> Result<(), CliError> {
762 let role_cli_overrides = parse_role_cli_overrides(std::env::args_os());
763 let extension_cli_overrides = parse_extension_cli_overrides(std::env::args_os());
764 let mut harness_config_overrides = parse_harness_config_cli_overrides(std::env::args_os())
765 .map_err(CliError::Participant)?;
766 let clap_command = cli::Cli::command();
767 let clap_matches = clap_command.get_matches();
768 let profile_was_explicitly_selected = matches!(
769 clap_matches.value_source("profile"),
770 Some(clap::parser::ValueSource::CommandLine)
771 );
772 let cli::Cli {
773 version,
774 harness,
775 run,
776 command,
777 } = cli::Cli::from_arg_matches(&clap_matches)
778 .expect("arguments accepted by Cli::command must parse as Cli");
779 let provider_alias_environment =
780 std::env::var_os(tau_config::settings::TAU_PROVIDER_ALIASES_ENV);
781 let model_alias_environment = std::env::var_os(tau_config::settings::TAU_MODEL_ALIASES_ENV);
782 let alias_input_presence = ModelReferenceAliasInputPresence {
783 provider_flag: !harness.provider_alias.is_empty(),
784 model_flag: !harness.model_alias.is_empty(),
785 provider_environment: provider_alias_environment.is_some(),
786 model_environment: model_alias_environment.is_some(),
787 };
788 if version {
789 reject_model_reference_alias_inputs(alias_input_presence, "version")?;
790 println!("{}", version_label());
791 return Ok(());
792 }
793 reject_legacy_config_path(run.config.as_deref())?;
794 if matches!(&command, Some(cli::Command::Serve { .. }))
795 && (run.ephemeral || run.prompt_stdin)
796 {
797 return Err(CliError::Participant(
798 "`tau serve` does not accept --ephemeral or --prompt-stdin".to_owned(),
799 ));
800 }
801 let command = match command {
802 Some(cli::Command::Run(args)) => DispatchCommand::Startup {
803 args,
804 mode: StartupMode::New,
805 },
806 Some(cli::Command::Attach { session }) => DispatchCommand::Startup {
807 args: run,
808 mode: StartupMode::Attach(session),
809 },
810 Some(cli::Command::Resume { session }) => DispatchCommand::Startup {
811 args: run,
812 mode: StartupMode::Resume(session),
813 },
814 Some(command) => DispatchCommand::Other(command),
815 None => DispatchCommand::Startup {
816 args: run,
817 mode: StartupMode::New,
818 },
819 };
820 let direct_harness_component = matches!(
821 &command,
822 DispatchCommand::Other(cli::Command::Component { name, .. }) if name == "harness"
823 );
824 let attaching = matches!(
825 &command,
826 DispatchCommand::Startup {
827 mode: StartupMode::Attach(_),
828 ..
829 }
830 );
831 let dump_initial_prompt = matches!(
832 &command,
833 DispatchCommand::Other(cli::Command::Dev {
834 command: cli::DevCommand::DumpInitialPrompt { .. }
835 })
836 );
837 if rejects_model_reference_alias_inputs(&command) || direct_harness_component {
838 reject_model_reference_alias_inputs(
839 ModelReferenceAliasInputPresence {
840 provider_environment: !direct_harness_component
841 && !attaching
842 && alias_input_presence.provider_environment,
843 model_environment: !direct_harness_component
844 && !attaching
845 && alias_input_presence.model_environment,
846 ..alias_input_presence
847 },
848 if direct_harness_component {
849 "component harness"
850 } else if dump_initial_prompt {
851 "dev dump-initial-prompt"
852 } else {
853 "this command"
854 },
855 )?;
856 }
857 let alias_config_overrides = tau_config::settings::model_reference_alias_config_overrides(
858 tau_config::settings::ModelReferenceAliasSources {
859 provider_environment: (!direct_harness_component && !attaching)
860 .then_some(provider_alias_environment)
861 .flatten(),
862 model_environment: (!direct_harness_component && !attaching)
863 .then_some(model_alias_environment)
864 .flatten(),
865 provider_cli: &harness.provider_alias,
866 model_cli: &harness.model_alias,
867 },
868 )
869 .map_err(|error| CliError::Participant(error.to_string()))?;
870 harness_config_overrides.extend(alias_config_overrides);
871 if let DispatchCommand::Startup {
872 args,
873 mode: StartupMode::Attach(_),
874 } = &command
875 {
876 reject_harness_config_overrides(&harness_config_overrides, "attach")?;
877 reject_attach_startup_overrides(
878 args.prompt_stdin,
879 profile_was_explicitly_selected,
880 harness.role.as_deref(),
881 &role_cli_overrides,
882 &extension_cli_overrides,
883 )?;
884 }
885 let profile_was_selected = profile_was_explicitly_selected
886 || std::env::var_os(tau_config::settings::TAU_PROFILE_ENV).is_some();
887 let reads_extension_environment = match &command {
888 DispatchCommand::Startup {
889 mode: StartupMode::New | StartupMode::Resume(_),
890 ..
891 } => true,
892 DispatchCommand::Other(cli::Command::Dev {
893 command:
894 cli::DevCommand::PrintPrompt { .. }
895 | cli::DevCommand::PrintSystemPrompt
896 | cli::DevCommand::PrintSkills { .. }
897 | cli::DevCommand::PrintTools
898 | cli::DevCommand::PreviewDeclarations
899 | cli::DevCommand::Tmux { .. },
900 }) => true,
901 DispatchCommand::Other(cli::Command::Component { name, .. }) => name == "harness",
902 DispatchCommand::Other(cli::Command::Serve { .. }) => true,
903 _ => false,
904 };
905 let environment_extension_names = if reads_extension_environment {
906 tau_config::settings::parse_enable_extensions_env(std::env::var_os(
907 tau_config::settings::TAU_ENABLE_EXTENSIONS_ENV,
908 ))
909 .map_err(|error| CliError::Participant(error.to_string()))?
910 } else {
911 Vec::new()
912 };
913 if reads_extension_environment {
914 tau_config::settings::parse_tau_state_access_env(std::env::var_os(
915 tau_config::settings::TAU_EXTENSION_TAU_STATE_ACCESS_ENV,
916 ))
917 .map_err(|error| CliError::Participant(error.to_string()))?;
918 }
919 match &command {
920 DispatchCommand::Startup { .. }
921 | DispatchCommand::Other(cli::Command::Dev {
922 command:
923 cli::DevCommand::PrintPrompt { .. }
924 | cli::DevCommand::PrintSystemPrompt
925 | cli::DevCommand::PrintSkills { .. }
926 | cli::DevCommand::PrintTools
927 | cli::DevCommand::PreviewDeclarations,
928 }) => {}
929 DispatchCommand::Other(cli::Command::Session { command }) => {
930 let command_name = match command {
931 cli::SessionCommand::List(_) => "session list",
932 cli::SessionCommand::Kill { .. } => "session kill",
933 cli::SessionCommand::Show { .. } => "session show",
934 cli::SessionCommand::Stats { .. } => "session stats",
935 cli::SessionCommand::Cache(_) => "session cache",
936 };
937 reject_harness_config_overrides(&harness_config_overrides, command_name)?;
938 }
939 DispatchCommand::Other(cli::Command::Agent { command }) => {
940 let command_name = match command {
941 cli::AgentCommand::List(_) => "agent list",
942 cli::AgentCommand::Unload(_) => "agent unload",
943 cli::AgentCommand::Trace(_) => "agent trace",
944 cli::AgentCommand::Cache(_) => "agent cache",
945 cli::AgentCommand::Export(_) => "agent export",
946 };
947 reject_harness_config_overrides(&harness_config_overrides, command_name)?;
948 }
949 DispatchCommand::Other(cli::Command::Init { .. }) => {
950 reject_harness_config_overrides(&harness_config_overrides, "init")?;
951 }
952 DispatchCommand::Other(cli::Command::Provider { .. }) => {
953 reject_harness_config_overrides(&harness_config_overrides, "provider")?;
954 }
955 DispatchCommand::Other(cli::Command::Dev {
956 command: cli::DevCommand::Send { .. },
957 }) => {
958 reject_harness_config_overrides(&harness_config_overrides, "dev send")?;
959 }
960 DispatchCommand::Other(cli::Command::Dev {
961 command: cli::DevCommand::Papercut { .. },
962 }) => {
963 reject_harness_config_overrides(&harness_config_overrides, "dev papercut")?;
964 }
965 DispatchCommand::Other(cli::Command::Dev {
966 command: cli::DevCommand::Tmux { .. },
967 }) => {
968 reject_dev_tmux_startup_overrides(
969 profile_was_selected.then_some("selected"),
970 harness.role.as_deref(),
971 &role_cli_overrides,
972 &extension_cli_overrides,
973 &harness_config_overrides,
974 )?;
975 }
976 DispatchCommand::Other(cli::Command::Dev {
977 command: cli::DevCommand::DumpInitialPrompt { .. },
978 }) => {
979 reject_harness_config_overrides(
980 &harness_config_overrides,
981 "dev dump-initial-prompt",
982 )?;
983 }
984 DispatchCommand::Other(cli::Command::Component { .. }) => {
985 reject_harness_config_overrides(&harness_config_overrides, "component")?;
986 }
987 DispatchCommand::Other(cli::Command::Serve { .. }) => {}
988 DispatchCommand::Other(cli::Command::Run(_))
989 | DispatchCommand::Other(cli::Command::Attach { .. })
990 | DispatchCommand::Other(cli::Command::Resume { .. }) => {
991 unreachable!("startup variants normalize to DispatchCommand::Startup")
992 }
993 }
994
995 if let DispatchCommand::Other(cli::Command::Dev {
996 command: cli::DevCommand::Tmux { command },
997 }) = command
998 {
999 return dev_tmux::run(command);
1000 }
1001
1002 if matches!(
1005 &command,
1006 DispatchCommand::Other(cli::Command::Dev {
1007 command: cli::DevCommand::PreviewDeclarations,
1008 })
1009 ) {
1010 return preview_declarations::run(
1011 harness.profile.as_deref(),
1012 &role_cli_overrides,
1013 &extension_cli_overrides,
1014 &environment_extension_names,
1015 &harness_config_overrides,
1016 );
1017 }
1018 let consumes_harness_settings = consumes_harness_settings(&command);
1019 let selected_profile = if consumes_harness_settings {
1020 tau_config::settings::selected_profile(harness.profile.as_deref())
1021 .map_err(|error| CliError::Participant(error.to_string()))?
1022 } else {
1023 None
1024 };
1025 if consumes_harness_settings {
1026 tau_harness::validate_cli_overrides_with_profile(
1027 selected_profile.as_ref(),
1028 &role_cli_overrides,
1029 &extension_cli_overrides,
1030 &harness_config_overrides,
1031 )
1032 .map_err(|error| CliError::Participant(error.to_string()))?;
1033 }
1034 if reads_extension_environment && consumes_harness_settings {
1035 tau_harness::validate_extension_environment_and_cli_overrides_with_profile(
1036 selected_profile.as_ref(),
1037 &environment_extension_names,
1038 &extension_cli_overrides,
1039 &role_cli_overrides,
1040 &harness_config_overrides,
1041 )
1042 .map_err(|error| CliError::Participant(error.to_string()))?;
1043 }
1044 match command {
1045 DispatchCommand::Startup {
1046 args:
1047 cli::RunArgs {
1048 config,
1049 prompt_stdin,
1050 ephemeral,
1051 },
1052 mode: startup_mode,
1053 } => {
1054 reject_legacy_config_path(config.as_deref())?;
1055 reject_ephemeral_incompatible(ephemeral, &startup_mode)?;
1056 let (session_id, session_status) = match &startup_mode {
1057 StartupMode::Attach(session) => (
1058 crate::daemon::resolve_attach_session_id(session.as_ref())?,
1059 SessionLaunchStatus::Resumed,
1060 ),
1061 StartupMode::Resume(session) => (
1062 resolve_resume_session_id(session.as_ref())?,
1063 SessionLaunchStatus::Resumed,
1064 ),
1065 StartupMode::New => (
1066 crate::daemon::mint_session_id(&std::env::current_dir()?),
1067 SessionLaunchStatus::New,
1068 ),
1069 };
1070 let attach = matches!(startup_mode, StartupMode::Attach(_));
1071 if prompt_stdin {
1072 prompt_stdin::run_prompt_stdin(
1073 &session_id,
1074 attach,
1075 session_status,
1076 harness.role.as_deref(),
1077 crate::daemon::DaemonCliOverrides {
1078 profile: selected_profile.as_ref(),
1079 role: &role_cli_overrides,
1080 extension: &extension_cli_overrides,
1081 extension_environment: None,
1082 harness_config: &harness_config_overrides,
1083 memory_only_agent_store: false,
1084 },
1085 ephemeral,
1086 )
1087 } else {
1088 run_chat(
1089 &session_id,
1090 attach,
1091 session_status,
1092 harness.role.as_deref(),
1093 crate::daemon::DaemonCliOverrides {
1094 profile: selected_profile.as_ref(),
1095 role: &role_cli_overrides,
1096 extension: &extension_cli_overrides,
1097 extension_environment: None,
1098 harness_config: &harness_config_overrides,
1099 memory_only_agent_store: false,
1100 },
1101 ephemeral,
1102 )
1103 }
1104 }
1105
1106 DispatchCommand::Other(cli::Command::Session {
1107 command: cli::SessionCommand::Kill { session_id },
1108 }) => {
1109 reject_harness_config_overrides(&harness_config_overrides, "session kill")?;
1110 kill_session::run(&session_id)
1111 }
1112
1113 DispatchCommand::Other(cli::Command::Session {
1114 command: cli::SessionCommand::List(args),
1115 }) => {
1116 reject_harness_config_overrides(&harness_config_overrides, "session list")?;
1117 list_sessions::run(&args)
1118 }
1119
1120 DispatchCommand::Other(cli::Command::Session {
1121 command:
1122 cli::SessionCommand::Show {
1123 session_id,
1124 sessions_dir,
1125 },
1126 }) => {
1127 reject_harness_config_overrides(&harness_config_overrides, "session show")?;
1128 for line in tau_session_inspect::session_lines(sessions_dir, &session_id)? {
1129 println!("{line}");
1130 }
1131 Ok(())
1132 }
1133
1134 DispatchCommand::Other(cli::Command::Session {
1135 command:
1136 cli::SessionCommand::Stats {
1137 session,
1138 sessions_dir,
1139 },
1140 }) => {
1141 reject_harness_config_overrides(&harness_config_overrides, "session stats")?;
1142 let stats = tau_session_inspect::read_session_stats(&sessions_dir, &session)?
1143 .ok_or_else(|| {
1144 CliError::Participant(format!("session `{session}` not found"))
1145 })?;
1146 let output = serde_toon::to_string(&stats).map_err(|error| {
1147 CliError::Participant(format!("failed to serialize session stats: {error}"))
1148 })?;
1149 println!("{output}");
1150 Ok(())
1151 }
1152
1153 DispatchCommand::Other(cli::Command::Agent {
1154 command: cli::AgentCommand::List(args),
1155 }) => {
1156 reject_harness_config_overrides(&harness_config_overrides, "agent list")?;
1157 list_agents::run(&args)
1158 }
1159 DispatchCommand::Other(cli::Command::Agent {
1160 command: cli::AgentCommand::Unload(args),
1161 }) => {
1162 reject_harness_config_overrides(&harness_config_overrides, "agent unload")?;
1163 unload_agent::run(&args)
1164 }
1165 DispatchCommand::Other(cli::Command::Agent {
1166 command: cli::AgentCommand::Cache(args),
1167 }) => run_cache_report(
1168 args.options,
1169 tau_session_inspect::CacheScope::Agent {
1170 agent_id: args.agent_id,
1171 include_descendants: args.include_descendants,
1172 },
1173 ),
1174 DispatchCommand::Other(cli::Command::Agent {
1175 command:
1176 cli::AgentCommand::Export(cli::AgentExportArgs {
1177 command: cli::AgentExportCommand::Chat(args),
1178 }),
1179 }) => {
1180 reject_harness_config_overrides(&harness_config_overrides, "agent export chat")?;
1181 let chat = tau_session_inspect::load_agent_chat(&args.agents_dir, &args.agent_id)?;
1182 let output = if args.toons {
1183 chat.to_toon()?
1184 } else {
1185 chat.to_markdown()
1186 };
1187 line_output::stream_stdout(|writer| writer.write_all(output.as_bytes()))
1188 }
1189 DispatchCommand::Other(cli::Command::Session {
1190 command: cli::SessionCommand::Cache(args),
1191 }) => run_cache_report(
1192 args.options,
1193 tau_session_inspect::CacheScope::Session(args.session_id),
1194 ),
1195 DispatchCommand::Other(cli::Command::Agent {
1196 command: cli::AgentCommand::Trace(args),
1197 }) => {
1198 reject_harness_config_overrides(&harness_config_overrides, "agent trace")?;
1199 let mode = match args.mode {
1200 cli::AgentTraceMode::Lite => tau_session_inspect::AgentTraceMode::Lite,
1201 cli::AgentTraceMode::Full => tau_session_inspect::AgentTraceMode::Full,
1202 };
1203 let format = match args.format {
1204 cli::AgentTraceFormat::TauJsonl => {
1205 tau_session_inspect::AgentTraceFormat::TauJsonl
1206 }
1207 cli::AgentTraceFormat::OtlpJson => {
1208 tau_session_inspect::AgentTraceFormat::OtlpJson
1209 }
1210 cli::AgentTraceFormat::AgentToolsJsonl => {
1211 tau_session_inspect::AgentTraceFormat::AgentToolsJsonl(mode)
1212 }
1213 cli::AgentTraceFormat::AgentToolsToon => {
1214 tau_session_inspect::AgentTraceFormat::AgentToolsToon(mode)
1215 }
1216 cli::AgentTraceFormat::AgentPerformanceJsonl => {
1217 tau_session_inspect::AgentTraceFormat::AgentPerformanceJsonl
1218 }
1219 };
1220 validate_agent_trace_mode(args.format, args.mode)?;
1221 let mut output = tau_session_inspect::prepare_agent_trace(
1222 &args.agents_dir,
1223 &args.agent_id,
1224 if args.include_descendants {
1225 tau_session_inspect::DescendantSelection::Include
1226 } else {
1227 tau_session_inspect::DescendantSelection::RootOnly
1228 },
1229 format,
1230 )?;
1231 line_output::stream_stdout(|writer| output.copy_to(writer).map(|_| ()))
1232 }
1233
1234 DispatchCommand::Other(cli::Command::Init { force }) => {
1235 reject_harness_config_overrides(&harness_config_overrides, "init")?;
1236 run_init(force)
1237 }
1238
1239 DispatchCommand::Other(cli::Command::Provider { args }) => {
1240 reject_harness_config_overrides(&harness_config_overrides, "provider")?;
1241 tau_ext_provider_builtin::run_provider_cli(&args)
1242 .map_err(|e| CliError::Participant(e.to_string()))
1243 }
1244
1245 DispatchCommand::Other(cli::Command::Dev { command }) => match command {
1246 cli::DevCommand::Send { session_id, line } => {
1247 reject_harness_config_overrides(&harness_config_overrides, "dev send")?;
1248 send::run_send(&session_id, &line.join(" "))
1249 }
1250 cli::DevCommand::DumpInitialPrompt { out, message } => {
1251 reject_harness_config_overrides(
1252 &harness_config_overrides,
1253 "dev dump-initial-prompt",
1254 )?;
1255 tau_harness::dump_initial_prompt(&out, &message)?;
1256 println!("wrote {}", out.display());
1257 Ok(())
1258 }
1259 cli::DevCommand::PrintPrompt { enable_agents_md } => {
1260 print_prompt::run_print_prompt(
1261 harness.role.as_deref(),
1262 enable_agents_md,
1263 selected_profile.as_ref(),
1264 &role_cli_overrides,
1265 &extension_cli_overrides,
1266 &environment_extension_names,
1267 &harness_config_overrides,
1268 )
1269 }
1270 cli::DevCommand::PrintSystemPrompt => {
1271 let role =
1272 required_harness_role(harness.role.as_deref(), "print-system-prompt")?;
1273 print_prompt::run_print_system_prompt(
1274 role,
1275 selected_profile.as_ref(),
1276 &role_cli_overrides,
1277 &extension_cli_overrides,
1278 &environment_extension_names,
1279 &harness_config_overrides,
1280 )
1281 }
1282 cli::DevCommand::PrintSkills { format } => print_skills::run_print_skills(
1283 harness.role.as_deref(),
1284 format,
1285 selected_profile.as_ref(),
1286 &role_cli_overrides,
1287 &extension_cli_overrides,
1288 &environment_extension_names,
1289 &harness_config_overrides,
1290 ),
1291 cli::DevCommand::PrintTools => print_tools::run_print_tools(
1292 harness.role.as_deref(),
1293 selected_profile.as_ref(),
1294 &role_cli_overrides,
1295 &extension_cli_overrides,
1296 &environment_extension_names,
1297 &harness_config_overrides,
1298 ),
1299 cli::DevCommand::Papercut { command } => papercut::run(command),
1300 cli::DevCommand::PreviewDeclarations => {
1301 unreachable!("declaration inspection returns before runtime validation")
1302 }
1303 cli::DevCommand::Tmux { command } => {
1304 let _ = command;
1305 unreachable!("dev tmux dispatch returns before harness config validation")
1306 }
1307 },
1308
1309 DispatchCommand::Other(cli::Command::Component {
1310 name,
1311 initial_ui_stdio,
1312 }) => {
1313 reject_harness_config_overrides(&harness_config_overrides, "component")?;
1314 if harness.profile.is_some() {
1315 return Err(CliError::Participant(
1316 "`tau component` cannot apply --profile in-process; set TAU_PROFILE before starting the component"
1317 .to_owned(),
1318 ));
1319 }
1320 if initial_ui_stdio && name != "harness" {
1321 return Err(CliError::Participant(
1322 "--initial-ui-stdio is only valid for `tau component harness`".to_owned(),
1323 ));
1324 }
1325 if name == "harness" && initial_ui_stdio {
1326 ui_logging::init_stderr_from_env("tau_harness=info,tau_cli=info,warn");
1327 return run_harness_component(true)
1328 .map_err(|e| CliError::Participant(e.to_string()));
1329 }
1330 if name == "harness" {
1331 ui_logging::init_stderr_from_env("tau_harness=info,tau_cli=info,warn");
1332 return tau_harness::run_component_with_internal_tools_and_extension_cli_overrides(
1333 tau_harness_tools::builtin_handlers(),
1334 extension_cli_overrides.clone(),
1335 )
1336 .map_err(|error| CliError::Participant(error.to_string()));
1337 }
1338 let built_in_components = [Component {
1339 name: "harness",
1340 runner: run_harness_component_default,
1341 logging: ComponentLogging::CliStderr,
1342 }];
1343 let component = built_in_components
1344 .iter()
1345 .chain(components)
1346 .find(|component| component.name == name)
1347 .ok_or_else(|| {
1348 let available = built_in_components
1349 .iter()
1350 .chain(components)
1351 .map(|component| component.name)
1352 .collect::<Vec<_>>()
1353 .join(", ");
1354 CliError::Participant(format!(
1355 "unknown component: {name}\navailable: {available}"
1356 ))
1357 })?;
1358 match component.logging {
1359 ComponentLogging::CliStderr => {
1360 ui_logging::init_stderr_from_env("tau_harness=info,tau_cli=info,warn")
1361 }
1362 ComponentLogging::RunnerManaged => {}
1363 }
1364 (component.runner)().map_err(|e| CliError::Participant(e.to_string()))
1365 }
1366 DispatchCommand::Other(cli::Command::Serve {
1367 session,
1368 create,
1369 existing,
1370 create_or_existing: _,
1371 bootstrap_prompt_file,
1372 bootstrap_id,
1373 mirror_extension_stderr,
1374 }) => {
1375 ui_logging::init_stderr_from_env("tau_harness=info,tau_cli=info,warn");
1376 let options = tau_harness::FixedSessionServeOptions {
1377 session_id: &session,
1378 profile_selection: selected_profile.as_ref(),
1379 startup_role: harness.role.as_deref(),
1380 environment_extension_names: &environment_extension_names,
1381 extension_cli_overrides: &extension_cli_overrides,
1382 role_cli_overrides: &role_cli_overrides,
1383 harness_config_overrides: &harness_config_overrides,
1384 internal_tool_handlers: tau_harness_tools::builtin_handlers(),
1385 bootstrap: bootstrap_prompt_file
1386 .as_deref()
1387 .zip(bootstrap_id.as_ref())
1388 .map(|(prompt_file, id)| tau_harness::BootstrapPromptOptions {
1389 prompt_file,
1390 id,
1391 }),
1392 mirror_extension_stderr,
1393 };
1394 if create {
1395 tau_harness::run_create_session_component_with_internal_tools(options)
1396 } else if existing {
1397 tau_harness::run_existing_session_component_with_internal_tools(options)
1398 } else {
1399 tau_harness::run_create_or_existing_session_component_with_internal_tools(
1400 options,
1401 )
1402 }
1403 .map_err(|error| CliError::Participant(error.to_string()))
1404 }
1405 DispatchCommand::Other(
1406 cli::Command::Run(_) | cli::Command::Attach { .. } | cli::Command::Resume { .. },
1407 ) => {
1408 unreachable!("startup variants normalize to DispatchCommand::Startup")
1409 }
1410 }
1411 };
1412
1413 match run() {
1414 Ok(()) => ExitCode::SUCCESS,
1415 Err(error) => {
1416 if error.should_report_to_terminal() {
1417 eprintln!("error: {error}");
1418 }
1419 error.exit_code()
1420 }
1421 }
1422}
1423
1424fn run_cache_report(
1426 args: cli::CacheArgs,
1427 scope: tau_session_inspect::CacheScope,
1428) -> Result<(), CliError> {
1429 let options = tau_session_inspect::CacheOptions {
1430 state_dir: args.state_dir,
1431 scope,
1432 prompt: args.prompt,
1433 selection: tau_session_inspect::CacheSelection {
1434 since_unix_micros: args.since,
1435 until_unix_micros: args.until,
1436 model: args.model,
1437 operation: args.operation.map(|operation| match operation {
1438 cli::CacheOperation::Inference => tau_session_inspect::CacheOperation::Inference,
1439 cli::CacheOperation::StandaloneCompaction => {
1440 tau_session_inspect::CacheOperation::StandaloneCompaction
1441 }
1442 cli::CacheOperation::CacheRefresh => {
1443 tau_session_inspect::CacheOperation::CacheRefresh
1444 }
1445 }),
1446 attempt: args.attempt,
1447 require_exact_chain: args.require_exact_chain,
1448 group_by: args
1449 .group_by
1450 .into_iter()
1451 .map(|group| match group {
1452 cli::CacheGroup::Model => tau_session_inspect::CacheGroup::Model,
1453 cli::CacheGroup::Backend => tau_session_inspect::CacheGroup::Backend,
1454 cli::CacheGroup::Controls => tau_session_inspect::CacheGroup::Controls,
1455 })
1456 .collect(),
1457 },
1458 view: match args.view {
1459 cli::CacheView::Summary => tau_session_inspect::CacheView::Summary,
1460 cli::CacheView::Attribution => tau_session_inspect::CacheView::Attribution,
1461 cli::CacheView::Continuity => tau_session_inspect::CacheView::Continuity,
1462 cli::CacheView::Geometry => tau_session_inspect::CacheView::Geometry,
1463 cli::CacheView::Gaps => tau_session_inspect::CacheView::Gaps,
1464 },
1465 limits: tau_session_inspect::CacheScanLimits {
1466 compressed_file_bytes: args.max_compressed_bytes,
1467 decompressed_file_bytes: args.max_decompressed_bytes,
1468 total_decompressed_bytes: args.max_total_bytes,
1469 working_memory_bytes: args.max_memory_bytes,
1470 },
1471 producer_build: build_revision(),
1472 index: args.index,
1473 };
1474 let report =
1475 tau_session_inspect::read_cache_report(&options).map_err(|_| CliError::CacheInvalid)?;
1476 line_output::stream_stdout(|writer| match (args.format, args.view) {
1477 (cli::CacheFormat::Summary, cli::CacheView::Summary) => report.write_summary(writer),
1478 (cli::CacheFormat::Summary, _) | (cli::CacheFormat::Jsonl, _) => report.write_jsonl(writer),
1479 })?;
1480 if report.index_written() {
1481 eprintln!(
1482 "Wrote a private disposable cache index; it contains linkable evidence and must be deleted manually when no longer needed."
1483 );
1484 }
1485 if report.is_partial() {
1486 Err(CliError::CachePartial)
1487 } else {
1488 Ok(())
1489 }
1490}
1491
1492#[cfg(test)]
1497mod test_support;
1498#[cfg(test)]
1499mod tests;