Synced from monorepo

Changes:
- Non-blocking coding-data sharing upsell banner
- Consolidate remediation in Doctor
- Auto mode defers fail-closed gate asks to the classifier
- Coalesce marketplace list fetches
- Allow removing a marketplace source by name
- Contain hung git marketplace sources (timeouts, non-blocking refresh, unbrick modal)
- Label failed workspace RPCs with error_kind
- Drop redundant explicit tonic/prost deps from xai-grok-shell
- Report real exit codes for completed background shells
- Narrow the date-rollover reminder to date-bearing templates
- Wire toolOverrides through the session and agent
- Security: Bash(git:*) allowlist matches whole command chain by prefix
- Split prompt-trigger telemetry and record classifier provenance
- Raise connectors-manager timeout to 60s
- Auto classifier honors recorded approvals for repeat actions
- Apply doctor fixes in the TUI
- Auto-mode classifier timeouts prompt instead of silently denying
- Scope subagent completion drains to the owning session
- Add the toolOverrides wire types
- Set client_identifier=grok-agent-sdk
- Accept both spellings of the workspace-teleport kill switch
- Persist one-shot occurrence journal
- Stop turns that poll the exact same tool call 16x in a row
- Copy compaction checkpoint files when forking sessions
- Auto-focus permission prompt from scrollback
- Esc cancels the running turn in non-vim and minimal modes
- List Ctrl+Z undo and redo in keyboard shortcuts
- Out-of-process macOS mic capture
- Show active auth mode on session-info
- Install the npm binary under $GROK_HOME
- Remove hover/click dead zones between dashboard items
- Route startup warnings to doctor
- Document [feedback.user] author identity config
- Extend bang command timeout
- Close combine-queued edit-hold race
- Integrate relocation recovery
- Expose privacy notice rollout flag
- Break harness discovery ref cycle so connections can idle-evict
- Shift/Alt+Enter inserts newline when editing a queued prompt
- Gate project Claude permissions on folder trust
- Echo response.create.event_id on response.created
- Toast when session creation fails from disk full
- Add shared test process lifecycle
- Enable dynamic workflows by default
- Add relocation transaction state machine
- Add shared test sandbox
- Surface auth failures on model-switch compact
- Persist durable scheduler expiry
- Confirm before removing extensions-modal items
- Re-run compact and prompt after login when compact hit expired auth
- Recap sends hosted tools under backend search
This commit is contained in:
grokkybara[bot] 2026-07-22 19:18:53 +01:00
commit a5727c5960
482 changed files with 37627 additions and 13402 deletions

View file

@ -602,8 +602,7 @@ mod tests {
item,
ConversationItem::User(u) if u.content.iter().any(|p| matches!(
p,
ContentPart::Text { text }
if text.as_ref() == IMAGE_COMPACT_PLACEHOLDER
ContentPart::Text { text } if text.as_ref() == IMAGE_COMPACT_PLACEHOLDER
))
)
}

View file

@ -101,8 +101,8 @@ pub fn truncate_trailing_incomplete_tool_call(
mut conversation: Vec<ConversationItem>,
) -> Vec<ConversationItem> {
while matches!(
conversation.last(), Some(ConversationItem::Assistant(a)) if ! a.tool_calls
.is_empty()
conversation.last(),
Some(ConversationItem::Assistant(a)) if !a.tool_calls.is_empty()
) {
conversation.pop();
}
@ -176,7 +176,8 @@ fn recover_truncated_tail_unit(
};
}
let owner = if matches!(
body.last(), Some(ConversationItem::Assistant(a)) if ! a.tool_calls.is_empty()
body.last(),
Some(ConversationItem::Assistant(a)) if !a.tool_calls.is_empty()
) {
body.pop()
} else {
@ -1065,9 +1066,11 @@ mod tests {
}
#[test]
fn compaction_attempt_defaults_optional_fields_for_old_artifacts() {
let json = serde_json::json!(
{ "attempt" : 1, "outcome" : "transient", "summary_chars" : 0, }
);
let json = serde_json::json!({
"attempt": 1,
"outcome": "transient",
"summary_chars": 0,
});
let parsed: CompactionAttempt = serde_json::from_value(json).unwrap();
assert_eq!(parsed.summary, None);
assert_eq!(parsed.error, None);
@ -1322,6 +1325,7 @@ actual user question";
"<user_info>OS: macos</user_info>\n<user_query>\nreal task\n</user_query>",
),
ConversationItem::assistant("done"),
// This is what run_inline_auto_continue() pushes after compaction:
ConversationItem::user(AUTO_CONTINUE_PROMPT),
ConversationItem::assistant("continuing..."),
];
@ -2240,7 +2244,9 @@ actual user question";
let items = vec![
ConversationItem::system("sys"),
ConversationItem::user("prompt"),
// Orphaned tool result — no assistant with matching tool_calls
ConversationItem::tool_result("call_ORPHAN", "result"),
// Valid pair
ConversationItem::assistant_tool_calls(vec![ToolCall {
id: "call_VALID".into(),
name: "read_file".to_string(),
@ -2354,6 +2360,7 @@ actual user question";
let mut items = vec![
ConversationItem::system("sys"),
ConversationItem::user("prompt"),
// ← the assistant declaring call_LOST is missing here
ConversationItem::tool_result("call_LOST", "orphaned result"),
ConversationItem::assistant_tool_calls(vec![call("call_OK")]),
ConversationItem::tool_result("call_OK", "fine"),
@ -2427,6 +2434,7 @@ actual user question";
ConversationItem::tool_result("call_A", "ok"),
ConversationItem::assistant_tool_calls(vec![call("call_C")]),
ConversationItem::tool_result("call_C", "ok"),
// call_B's owner was flushed two messages ago.
ConversationItem::tool_result("call_B", "displaced"),
];
let report = repair_history(&mut items);
@ -2678,13 +2686,18 @@ actual user question";
async fn build_compacted_history_multi_turn_with_parallel_tool_calls() {
use xai_grok_sampling_types::{AssistantItem, ToolCall};
let conversation = vec![
// [0] System prompt
ConversationItem::system("You are a helpful coding assistant."),
// [1] User info prefix (no <user_query> tags — this is the initial message)
ConversationItem::user(
"<user_info>\nOS Version: macos\nShell: /bin/bash\nWorkspace Path: /Users/dev/project\n</user_info>\n\n<project_layout>\n/Users/dev/project/\n src/\n main.rs\n lib.rs\n</project_layout>",
),
// ── Turn 1 ──────────────────────────────────────────────────
// [2] User query (wrapped in <user_query> tags by parse_prompt)
ConversationItem::user(
"<user_query>\nRead main.rs and lib.rs and tell me what they do\n</user_query>",
),
// [3] Assistant with 2 parallel tool calls
ConversationItem::Assistant(AssistantItem {
content: "I'll read both files for you.".into(),
tool_calls: vec![
@ -2703,20 +2716,26 @@ actual user question";
model_fingerprint: None,
reasoning_effort: None,
}),
// [4] Tool result for call_1
ConversationItem::tool_result(
"call_1",
"fn main() {\n println!(\"hello world\");\n}",
),
// [5] Tool result for call_2
ConversationItem::tool_result(
"call_2",
"pub fn add(a: i32, b: i32) -> i32 {\n a + b\n}",
),
// [6] Assistant summary after reading both files
ConversationItem::assistant(
"main.rs prints hello world. lib.rs has an `add` function.",
),
// ── Turn 2 ──────────────────────────────────────────────────
// [7] User query (second turn)
ConversationItem::user(
"<user_query>\nNow fix the typo in main.rs and run the tests\n</user_query>",
),
// [8] Assistant with 2 parallel tool calls
ConversationItem::Assistant(AssistantItem {
content: "I'll fix the typo and run tests.".into(),
tool_calls: vec![
@ -2736,11 +2755,14 @@ actual user question";
model_fingerprint: None,
reasoning_effort: None,
}),
// [9] Tool result for call_3
ConversationItem::tool_result("call_3", "File edited successfully."),
// [10] Tool result for call_4
ConversationItem::tool_result(
"call_4",
"running 1 test\ntest tests::test_add ... ok\n\ntest result: ok. 1 passed",
),
// [11] Assistant final response
ConversationItem::assistant("Fixed the typo and all tests pass!"),
];
let mut edited = BTreeSet::new();
@ -2782,8 +2804,7 @@ actual user question";
});
assert_eq!(compacted.len(), 9, "compacted history should have 9 items");
assert!(
matches!(& compacted[0], ConversationItem::System(s) if s.content.as_ref() ==
"You are a helpful coding assistant.")
matches!(&compacted[0], ConversationItem::System(s) if s.content.as_ref() == "You are a helpful coding assistant.")
);
let prefix = compacted[1].text_content();
assert!(
@ -2948,8 +2969,7 @@ The user asked to read main.rs and lib.rs. main.rs prints hello world, lib.rs ha
summary_count: 1,
});
assert!(!compacted.iter().any(|item| {
matches!(item, ConversationItem::User(user)
if user.synthetic_reason == Some(SyntheticReason::ProjectInstructions))
matches!(item, ConversationItem::User(user) if user.synthetic_reason == Some(SyntheticReason::ProjectInstructions))
}));
}
/// The AGENTS.md slot must use the structural project-instructions tag.
@ -3022,8 +3042,9 @@ The user asked to read main.rs and lib.rs. main.rs prints hello world, lib.rs ha
});
let has_project_instructions = compacted.iter().any(|item| {
matches!(
item, ConversationItem::User(u) if u.synthetic_reason ==
Some(SyntheticReason::ProjectInstructions)
item,
ConversationItem::User(u)
if u.synthetic_reason == Some(SyntheticReason::ProjectInstructions)
)
});
assert!(
@ -3392,11 +3413,9 @@ The user asked to read main.rs and lib.rs. main.rs prints hello world, lib.rs ha
ConversationItem::tool_result("c1", "fn main() {}"),
];
let has_tool_calls = |items: &[ConversationItem]| {
items.iter().any(|i| {
matches!(
i, ConversationItem::Assistant(a) if ! a.tool_calls.is_empty()
)
})
items
.iter()
.any(|i| matches!(i, ConversationItem::Assistant(a) if !a.tool_calls.is_empty()))
};
let has_tool_result = |items: &[ConversationItem]| {
items
@ -3405,10 +3424,8 @@ The user asked to read main.rs and lib.rs. main.rs prints hello world, lib.rs ha
};
let has_image = |items: &[ConversationItem]| {
items.iter().any(|i| {
matches!(
i, ConversationItem::User(u) if u.content.iter().any(| p |
matches!(p, ContentPart::Image { .. }))
)
matches!(i, ConversationItem::User(u)
if u.content.iter().any(|p| matches!(p, ContentPart::Image { .. })))
})
};
let seg = prepare_conversation_for_segment(conv.clone());
@ -3514,6 +3531,7 @@ The user asked to read main.rs and lib.rs. main.rs prints hello world, lib.rs ha
arguments: r#"{"target_file":"a.rs"}"#.into(),
}]),
ConversationItem::tool_result("c1", "fn main() {}"),
// Trailing, no matching ToolResult — results never arrived.
ConversationItem::assistant_tool_calls(vec![ToolCall {
id: "c2".into(),
name: "grep".to_string(),
@ -3564,8 +3582,8 @@ The user asked to read main.rs and lib.rs. main.rs prints hello world, lib.rs ha
let big = "x".repeat(800);
let conv = vec![
ConversationItem::system("sys"),
ConversationItem::user(&big),
ConversationItem::assistant(&big),
ConversationItem::user(&big), // old + large -> dropped
ConversationItem::assistant(&big), // old + large -> dropped
ConversationItem::user("recent question"),
ConversationItem::assistant("recent answer"),
];
@ -3620,7 +3638,7 @@ The user asked to read main.rs and lib.rs. main.rs prints hello world, lib.rs ha
name: "read_file".to_string(),
arguments: "{}".into(),
}]),
ConversationItem::tool_result("c1", huge.as_str()),
ConversationItem::tool_result("c1", huge.as_str()), // triggering result
];
let out = fit_conversation_to_budget(conv, 100);
let tr = out
@ -3640,8 +3658,7 @@ The user asked to read main.rs and lib.rs. main.rs prints hello world, lib.rs ha
);
assert!(
out.iter()
.any(|i| matches!(i, ConversationItem::Assistant(a) if ! a
.tool_calls.is_empty())),
.any(|i| matches!(i, ConversationItem::Assistant(a) if !a.tool_calls.is_empty())),
"owning assistant tool_use must be kept so the result is not orphaned"
);
let est: u64 = out.iter().map(estimate_item_tokens).sum();
@ -3678,15 +3695,17 @@ The user asked to read main.rs and lib.rs. main.rs prints hello world, lib.rs ha
}
let conv = vec![
ConversationItem::system("sys"),
img_user,
img_user, // old turn, huge by image charges, ~0 by text bytes
ConversationItem::user("recent question"),
ConversationItem::assistant("recent answer"),
];
let out = fit_conversation_to_budget(conv, 1_000);
assert!(
!out.iter()
.any(|i| matches!(i, ConversationItem::User(u) if u.content
.iter().any(| p | matches!(p, ContentPart::Image { .. })))),
!out.iter().any(|i| matches!(
i,
ConversationItem::User(u)
if u.content.iter().any(|p| matches!(p, ContentPart::Image { .. }))
)),
"image-heavy old turn must be counted (765/image) and trimmed, not kept"
);
assert!(
@ -3708,7 +3727,7 @@ The user asked to read main.rs and lib.rs. main.rs prints hello world, lib.rs ha
});
let conv = vec![
ConversationItem::system("sys"),
reasoning,
reasoning, // old turn, huge by encrypted bytes, 0 by visible text
ConversationItem::user("recent question"),
ConversationItem::assistant("recent answer"),
];