Skip to content

Commit d575b0d

Browse files
authored
feat: add introspect tool for Q CLI self-awareness (#2677)
- Add introspect tool with comprehensive Q CLI documentation - Include auto-tangent mode for isolated introspect conversations - Add GitHub links for documentation references - Support agent file locations and built-in tools documentation - Add automatic settings documentation with native enum descriptions - Use strum EnumMessage for maintainable setting descriptions
1 parent aa84de0 commit d575b0d

File tree

6 files changed

+303
-252
lines changed

6 files changed

+303
-252
lines changed

crates/chat-cli/src/cli/chat/mod.rs

Lines changed: 75 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -19,147 +19,60 @@ pub mod tool_manager;
1919
pub mod tools;
2020
pub mod util;
2121
use std::borrow::Cow;
22-
use std::collections::{
23-
HashMap,
24-
VecDeque,
25-
};
26-
use std::io::{
27-
IsTerminal,
28-
Read,
29-
Write,
30-
};
22+
use std::collections::{HashMap, VecDeque};
23+
use std::io::{IsTerminal, Read, Write};
3124
use std::process::ExitCode;
3225
use std::sync::Arc;
33-
use std::time::{
34-
Duration,
35-
Instant,
36-
};
26+
use std::time::{Duration, Instant};
3727

3828
use amzn_codewhisperer_client::types::SubscriptionStatus;
39-
use clap::{
40-
Args,
41-
CommandFactory,
42-
Parser,
43-
};
29+
use clap::{Args, CommandFactory, Parser};
4430
use cli::compact::CompactStrategy;
45-
use cli::model::{
46-
get_available_models,
47-
select_model,
48-
};
31+
use cli::model::{get_available_models, select_model};
4932
pub use conversation::ConversationState;
5033
use conversation::TokenWarningLevel;
51-
use crossterm::style::{
52-
Attribute,
53-
Color,
54-
Stylize,
55-
};
56-
use crossterm::{
57-
cursor,
58-
execute,
59-
queue,
60-
style,
61-
terminal,
62-
};
63-
use eyre::{
64-
Report,
65-
Result,
66-
bail,
67-
eyre,
68-
};
34+
use crossterm::style::{Attribute, Color, Stylize};
35+
use crossterm::{cursor, execute, queue, style, terminal};
36+
use eyre::{Report, Result, bail, eyre};
6937
use input_source::InputSource;
70-
use message::{
71-
AssistantMessage,
72-
AssistantToolUse,
73-
ToolUseResult,
74-
ToolUseResultBlock,
75-
};
76-
use parse::{
77-
ParseState,
78-
interpret_markdown,
79-
};
80-
use parser::{
81-
RecvErrorKind,
82-
RequestMetadata,
83-
SendMessageStream,
84-
};
38+
use message::{AssistantMessage, AssistantToolUse, ToolUseResult, ToolUseResultBlock};
39+
use parse::{ParseState, interpret_markdown};
40+
use parser::{RecvErrorKind, RequestMetadata, SendMessageStream};
8541
use regex::Regex;
86-
use spinners::{
87-
Spinner,
88-
Spinners,
89-
};
42+
use spinners::{Spinner, Spinners};
9043
use thiserror::Error;
9144
use time::OffsetDateTime;
9245
use token_counter::TokenCounter;
9346
use tokio::signal::ctrl_c;
94-
use tokio::sync::{
95-
Mutex,
96-
broadcast,
97-
};
98-
use tool_manager::{
99-
PromptQuery,
100-
PromptQueryResult,
101-
ToolManager,
102-
ToolManagerBuilder,
103-
};
47+
use tokio::sync::{Mutex, broadcast};
48+
use tool_manager::{PromptQuery, PromptQueryResult, ToolManager, ToolManagerBuilder};
10449
use tools::gh_issue::GhIssueContext;
105-
use tools::{
106-
NATIVE_TOOLS,
107-
OutputKind,
108-
QueuedTool,
109-
Tool,
110-
ToolSpec,
111-
};
112-
use tracing::{
113-
debug,
114-
error,
115-
info,
116-
trace,
117-
warn,
118-
};
50+
use tools::{NATIVE_TOOLS, OutputKind, QueuedTool, Tool, ToolSpec};
51+
use tracing::{debug, error, info, trace, warn};
11952
use util::images::RichImageBlock;
12053
use util::ui::draw_box;
121-
use util::{
122-
animate_output,
123-
play_notification_bell,
124-
};
54+
use util::{animate_output, play_notification_bell};
12555
use winnow::Partial;
12656
use winnow::stream::Offset;
12757

128-
use super::agent::{
129-
DEFAULT_AGENT_NAME,
130-
PermissionEvalResult,
131-
};
58+
use super::agent::{DEFAULT_AGENT_NAME, PermissionEvalResult};
13259
use crate::api_client::model::ToolResultStatus;
133-
use crate::api_client::{
134-
self,
135-
ApiClientError,
136-
};
60+
use crate::api_client::{self, ApiClientError};
13761
use crate::auth::AuthError;
13862
use crate::auth::builder_id::is_idc_user;
13963
use crate::cli::agent::Agents;
14064
use crate::cli::chat::cli::SlashCommand;
14165
use crate::cli::chat::cli::model::find_model;
142-
use crate::cli::chat::cli::prompts::{
143-
GetPromptError,
144-
PromptsSubcommand,
145-
};
66+
use crate::cli::chat::cli::prompts::{GetPromptError, PromptsSubcommand};
14667
use crate::cli::chat::util::sanitize_unicode_tags;
14768
use crate::database::settings::Setting;
14869
use crate::mcp_client::Prompt;
14970
use crate::os::Os;
15071
use crate::telemetry::core::{
151-
AgentConfigInitArgs,
152-
ChatAddedMessageParams,
153-
ChatConversationType,
154-
MessageMetaTag,
155-
RecordUserTurnCompletionArgs,
72+
AgentConfigInitArgs, ChatAddedMessageParams, ChatConversationType, MessageMetaTag, RecordUserTurnCompletionArgs,
15673
ToolUseEventBuilder,
15774
};
158-
use crate::telemetry::{
159-
ReasonCode,
160-
TelemetryResult,
161-
get_error_reason,
162-
};
75+
use crate::telemetry::{ReasonCode, TelemetryResult, get_error_reason};
16376
use crate::util::MCP_SERVER_TOOL_DELIMITER;
16477

16578
const LIMIT_REACHED_TEXT: &str = color_print::cstr! { "You've used all your free requests for this month. You have two options:
@@ -272,13 +185,17 @@ impl ChatArgs {
272185
agents.trust_all_tools = self.trust_all_tools;
273186

274187
os.telemetry
275-
.send_agent_config_init(&os.database, conversation_id.clone(), AgentConfigInitArgs {
276-
agents_loaded_count: md.load_count as i64,
277-
agents_loaded_failed_count: md.load_failed_count as i64,
278-
legacy_profile_migration_executed: md.migration_performed,
279-
legacy_profile_migrated_count: md.migrated_count as i64,
280-
launched_agent: md.launched_agent,
281-
})
188+
.send_agent_config_init(
189+
&os.database,
190+
conversation_id.clone(),
191+
AgentConfigInitArgs {
192+
agents_loaded_count: md.load_count as i64,
193+
agents_loaded_failed_count: md.load_failed_count as i64,
194+
legacy_profile_migration_executed: md.migration_performed,
195+
legacy_profile_migrated_count: md.migrated_count as i64,
196+
launched_agent: md.launched_agent,
197+
},
198+
)
282199
.await
283200
.map_err(|err| error!(?err, "failed to send agent config init telemetry"))
284201
.ok();
@@ -403,7 +320,7 @@ const SMALL_SCREEN_WELCOME_TEXT: &str = color_print::cstr! {"<em>Welcome to <cya
403320
const RESUME_TEXT: &str = color_print::cstr! {"<em>Picking up where we left off...</em>"};
404321

405322
// Only show the model-related tip for now to make users aware of this feature.
406-
const ROTATING_TIPS: [&str; 17] = [
323+
const ROTATING_TIPS: [&str; 18] = [
407324
color_print::cstr! {"You can resume the last conversation from your current directory by launching with
408325
<green!>q chat --resume</green!>"},
409326
color_print::cstr! {"Get notified whenever Q CLI finishes responding.
@@ -436,6 +353,7 @@ const ROTATING_TIPS: [&str; 17] = [
436353
color_print::cstr! {"Set a default model by running <green!>q settings chat.defaultModel MODEL</green!>. Run <green!>/model</green!> to learn more."},
437354
color_print::cstr! {"Run <green!>/prompts</green!> to learn how to build & run repeatable workflows"},
438355
color_print::cstr! {"Use <green!>/tangent</green!> or <green!>ctrl + t</green!> (customizable) to start isolated conversations ( ↯ ) that don't affect your main chat history"},
356+
color_print::cstr! {"Ask me directly about my capabilities! Try questions like <green!>\"What can you do?\"</green!> or <green!>\"Can you save conversations?\"</green!>"},
439357
];
440358

441359
const GREETING_BREAK_POINT: usize = 80;
@@ -1845,6 +1763,21 @@ impl ChatSession {
18451763
}
18461764

18471765
async fn tool_use_execute(&mut self, os: &mut Os) -> Result<ChatState, ChatError> {
1766+
// Check if we should auto-enter tangent mode for introspect tool
1767+
if os
1768+
.database
1769+
.settings
1770+
.get_bool(Setting::IntrospectTangentMode)
1771+
.unwrap_or(false)
1772+
&& !self.conversation.is_in_tangent_mode()
1773+
&& self
1774+
.tool_uses
1775+
.iter()
1776+
.any(|tool| matches!(tool.tool, Tool::Introspect(_)))
1777+
{
1778+
self.conversation.enter_tangent_mode();
1779+
}
1780+
18481781
// Verify tools have permissions.
18491782
for i in 0..self.tool_uses.len() {
18501783
let tool = &mut self.tool_uses[i];
@@ -2777,26 +2710,31 @@ impl ChatSession {
27772710
};
27782711

27792712
os.telemetry
2780-
.send_record_user_turn_completion(&os.database, conversation_id, result, RecordUserTurnCompletionArgs {
2781-
message_ids: mds.iter().map(|md| md.message_id.clone()).collect::<_>(),
2782-
request_ids: mds.iter().map(|md| md.request_id.clone()).collect::<_>(),
2783-
reason,
2784-
reason_desc,
2785-
status_code,
2786-
time_to_first_chunks_ms: mds
2787-
.iter()
2788-
.map(|md| md.time_to_first_chunk.map(|d| d.as_secs_f64() * 1000.0))
2789-
.collect::<_>(),
2790-
chat_conversation_type: md.and_then(|md| md.chat_conversation_type),
2791-
assistant_response_length: mds.iter().map(|md| md.response_size as i64).sum(),
2792-
message_meta_tags: mds.last().map(|md| md.message_meta_tags.clone()).unwrap_or_default(),
2793-
user_prompt_length: mds.first().map(|md| md.user_prompt_length).unwrap_or_default() as i64,
2794-
user_turn_duration_seconds,
2795-
follow_up_count: mds
2796-
.iter()
2797-
.filter(|md| matches!(md.chat_conversation_type, Some(ChatConversationType::ToolUse)))
2798-
.count() as i64,
2799-
})
2713+
.send_record_user_turn_completion(
2714+
&os.database,
2715+
conversation_id,
2716+
result,
2717+
RecordUserTurnCompletionArgs {
2718+
message_ids: mds.iter().map(|md| md.message_id.clone()).collect::<_>(),
2719+
request_ids: mds.iter().map(|md| md.request_id.clone()).collect::<_>(),
2720+
reason,
2721+
reason_desc,
2722+
status_code,
2723+
time_to_first_chunks_ms: mds
2724+
.iter()
2725+
.map(|md| md.time_to_first_chunk.map(|d| d.as_secs_f64() * 1000.0))
2726+
.collect::<_>(),
2727+
chat_conversation_type: md.and_then(|md| md.chat_conversation_type),
2728+
assistant_response_length: mds.iter().map(|md| md.response_size as i64).sum(),
2729+
message_meta_tags: mds.last().map(|md| md.message_meta_tags.clone()).unwrap_or_default(),
2730+
user_prompt_length: mds.first().map(|md| md.user_prompt_length).unwrap_or_default() as i64,
2731+
user_turn_duration_seconds,
2732+
follow_up_count: mds
2733+
.iter()
2734+
.filter(|md| matches!(md.chat_conversation_type, Some(ChatConversationType::ToolUse)))
2735+
.count() as i64,
2736+
},
2737+
)
28002738
.await
28012739
.ok();
28022740
}

0 commit comments

Comments
 (0)